Move input library code from frameworks/base.
Change-Id: I4983db61b53e28479fc90d9211fafff68f7f49a6
This commit is contained in:
parent
23e81a2103
commit
5912f95d26
622
include/input/Input.h
Normal file
622
include/input/Input.h
Normal file
@ -0,0 +1,622 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_INPUT_H
|
||||
#define _LIBINPUT_INPUT_H
|
||||
|
||||
/**
|
||||
* Native input event structures.
|
||||
*/
|
||||
|
||||
#include <android/input.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
class SkMatrix;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Additional private constants not defined in ndk/ui/input.h.
|
||||
*/
|
||||
enum {
|
||||
/* Signifies that the key is being predispatched */
|
||||
AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
|
||||
|
||||
/* Private control to determine when an app is tracking a key sequence. */
|
||||
AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
|
||||
|
||||
/* Key event is inconsistent with previously sent key events. */
|
||||
AKEY_EVENT_FLAG_TAINTED = 0x80000000,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* Motion event is inconsistent with previously sent motion events. */
|
||||
AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* Used when a motion event is not associated with any display.
|
||||
* Typically used for non-pointer events. */
|
||||
ADISPLAY_ID_NONE = -1,
|
||||
|
||||
/* The default display id. */
|
||||
ADISPLAY_ID_DEFAULT = 0,
|
||||
};
|
||||
|
||||
enum {
|
||||
/*
|
||||
* Indicates that an input device has switches.
|
||||
* This input source flag is hidden from the API because switches are only used by the system
|
||||
* and applications have no way to interact with them.
|
||||
*/
|
||||
AINPUT_SOURCE_SWITCH = 0x80000000,
|
||||
};
|
||||
|
||||
/*
|
||||
* SystemUiVisibility constants from View.
|
||||
*/
|
||||
enum {
|
||||
ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE = 0,
|
||||
ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN = 0x00000001,
|
||||
};
|
||||
|
||||
/*
|
||||
* Maximum number of pointers supported per motion event.
|
||||
* Smallest number of pointers is 1.
|
||||
* (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
|
||||
* will occasionally emit 11. There is not much harm making this constant bigger.)
|
||||
*/
|
||||
#define MAX_POINTERS 16
|
||||
|
||||
/*
|
||||
* Maximum pointer id value supported in a motion event.
|
||||
* Smallest pointer id is 0.
|
||||
* (This is limited by our use of BitSet32 to track pointer assignments.)
|
||||
*/
|
||||
#define MAX_POINTER_ID 31
|
||||
|
||||
/*
|
||||
* Declare a concrete type for the NDK's input event forward declaration.
|
||||
*/
|
||||
struct AInputEvent {
|
||||
virtual ~AInputEvent() { }
|
||||
};
|
||||
|
||||
/*
|
||||
* Declare a concrete type for the NDK's input device forward declaration.
|
||||
*/
|
||||
struct AInputDevice {
|
||||
virtual ~AInputDevice() { }
|
||||
};
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
class Parcel;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Flags that flow alongside events in the input dispatch system to help with certain
|
||||
* policy decisions such as waking from device sleep.
|
||||
*
|
||||
* These flags are also defined in frameworks/base/core/java/android/view/WindowManagerPolicy.java.
|
||||
*/
|
||||
enum {
|
||||
/* These flags originate in RawEvents and are generally set in the key map.
|
||||
* NOTE: If you edit these flags, also edit labels in KeycodeLabels.h. */
|
||||
|
||||
POLICY_FLAG_WAKE = 0x00000001,
|
||||
POLICY_FLAG_WAKE_DROPPED = 0x00000002,
|
||||
POLICY_FLAG_SHIFT = 0x00000004,
|
||||
POLICY_FLAG_CAPS_LOCK = 0x00000008,
|
||||
POLICY_FLAG_ALT = 0x00000010,
|
||||
POLICY_FLAG_ALT_GR = 0x00000020,
|
||||
POLICY_FLAG_MENU = 0x00000040,
|
||||
POLICY_FLAG_LAUNCHER = 0x00000080,
|
||||
POLICY_FLAG_VIRTUAL = 0x00000100,
|
||||
POLICY_FLAG_FUNCTION = 0x00000200,
|
||||
|
||||
POLICY_FLAG_RAW_MASK = 0x0000ffff,
|
||||
|
||||
/* These flags are set by the input dispatcher. */
|
||||
|
||||
// Indicates that the input event was injected.
|
||||
POLICY_FLAG_INJECTED = 0x01000000,
|
||||
|
||||
// Indicates that the input event is from a trusted source such as a directly attached
|
||||
// input device or an application with system-wide event injection permission.
|
||||
POLICY_FLAG_TRUSTED = 0x02000000,
|
||||
|
||||
// Indicates that the input event has passed through an input filter.
|
||||
POLICY_FLAG_FILTERED = 0x04000000,
|
||||
|
||||
// Disables automatic key repeating behavior.
|
||||
POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000,
|
||||
|
||||
/* These flags are set by the input reader policy as it intercepts each event. */
|
||||
|
||||
// Indicates that the screen was off when the event was received and the event
|
||||
// should wake the device.
|
||||
POLICY_FLAG_WOKE_HERE = 0x10000000,
|
||||
|
||||
// Indicates that the screen was dim when the event was received and the event
|
||||
// should brighten the device.
|
||||
POLICY_FLAG_BRIGHT_HERE = 0x20000000,
|
||||
|
||||
// Indicates that the event should be dispatched to applications.
|
||||
// The input event should still be sent to the InputDispatcher so that it can see all
|
||||
// input events received include those that it will not deliver.
|
||||
POLICY_FLAG_PASS_TO_USER = 0x40000000,
|
||||
};
|
||||
|
||||
/*
|
||||
* Pointer coordinate data.
|
||||
*/
|
||||
struct PointerCoords {
|
||||
enum { MAX_AXES = 14 }; // 14 so that sizeof(PointerCoords) == 64
|
||||
|
||||
// Bitfield of axes that are present in this structure.
|
||||
uint64_t bits;
|
||||
|
||||
// Values of axes that are stored in this structure packed in order by axis id
|
||||
// for each axis that is present in the structure according to 'bits'.
|
||||
float values[MAX_AXES];
|
||||
|
||||
inline void clear() {
|
||||
bits = 0;
|
||||
}
|
||||
|
||||
float getAxisValue(int32_t axis) const;
|
||||
status_t setAxisValue(int32_t axis, float value);
|
||||
|
||||
void scale(float scale);
|
||||
|
||||
inline float getX() const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_X);
|
||||
}
|
||||
|
||||
inline float getY() const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_Y);
|
||||
}
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
status_t readFromParcel(Parcel* parcel);
|
||||
status_t writeToParcel(Parcel* parcel) const;
|
||||
#endif
|
||||
|
||||
bool operator==(const PointerCoords& other) const;
|
||||
inline bool operator!=(const PointerCoords& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void copyFrom(const PointerCoords& other);
|
||||
|
||||
private:
|
||||
void tooManyAxes(int axis);
|
||||
};
|
||||
|
||||
/*
|
||||
* Pointer property data.
|
||||
*/
|
||||
struct PointerProperties {
|
||||
// The id of the pointer.
|
||||
int32_t id;
|
||||
|
||||
// The pointer tool type.
|
||||
int32_t toolType;
|
||||
|
||||
inline void clear() {
|
||||
id = -1;
|
||||
toolType = 0;
|
||||
}
|
||||
|
||||
bool operator==(const PointerProperties& other) const;
|
||||
inline bool operator!=(const PointerProperties& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void copyFrom(const PointerProperties& other);
|
||||
};
|
||||
|
||||
/*
|
||||
* Input events.
|
||||
*/
|
||||
class InputEvent : public AInputEvent {
|
||||
public:
|
||||
virtual ~InputEvent() { }
|
||||
|
||||
virtual int32_t getType() const = 0;
|
||||
|
||||
inline int32_t getDeviceId() const { return mDeviceId; }
|
||||
|
||||
inline int32_t getSource() const { return mSource; }
|
||||
|
||||
inline void setSource(int32_t source) { mSource = source; }
|
||||
|
||||
protected:
|
||||
void initialize(int32_t deviceId, int32_t source);
|
||||
void initialize(const InputEvent& from);
|
||||
|
||||
int32_t mDeviceId;
|
||||
int32_t mSource;
|
||||
};
|
||||
|
||||
/*
|
||||
* Key events.
|
||||
*/
|
||||
class KeyEvent : public InputEvent {
|
||||
public:
|
||||
virtual ~KeyEvent() { }
|
||||
|
||||
virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; }
|
||||
|
||||
inline int32_t getAction() const { return mAction; }
|
||||
|
||||
inline int32_t getFlags() const { return mFlags; }
|
||||
|
||||
inline void setFlags(int32_t flags) { mFlags = flags; }
|
||||
|
||||
inline int32_t getKeyCode() const { return mKeyCode; }
|
||||
|
||||
inline int32_t getScanCode() const { return mScanCode; }
|
||||
|
||||
inline int32_t getMetaState() const { return mMetaState; }
|
||||
|
||||
inline int32_t getRepeatCount() const { return mRepeatCount; }
|
||||
|
||||
inline nsecs_t getDownTime() const { return mDownTime; }
|
||||
|
||||
inline nsecs_t getEventTime() const { return mEventTime; }
|
||||
|
||||
// Return true if this event may have a default action implementation.
|
||||
static bool hasDefaultAction(int32_t keyCode);
|
||||
bool hasDefaultAction() const;
|
||||
|
||||
// Return true if this event represents a system key.
|
||||
static bool isSystemKey(int32_t keyCode);
|
||||
bool isSystemKey() const;
|
||||
|
||||
void initialize(
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t keyCode,
|
||||
int32_t scanCode,
|
||||
int32_t metaState,
|
||||
int32_t repeatCount,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime);
|
||||
void initialize(const KeyEvent& from);
|
||||
|
||||
protected:
|
||||
int32_t mAction;
|
||||
int32_t mFlags;
|
||||
int32_t mKeyCode;
|
||||
int32_t mScanCode;
|
||||
int32_t mMetaState;
|
||||
int32_t mRepeatCount;
|
||||
nsecs_t mDownTime;
|
||||
nsecs_t mEventTime;
|
||||
};
|
||||
|
||||
/*
|
||||
* Motion events.
|
||||
*/
|
||||
class MotionEvent : public InputEvent {
|
||||
public:
|
||||
virtual ~MotionEvent() { }
|
||||
|
||||
virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; }
|
||||
|
||||
inline int32_t getAction() const { return mAction; }
|
||||
|
||||
inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
|
||||
|
||||
inline int32_t getActionIndex() const {
|
||||
return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
|
||||
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
|
||||
inline void setAction(int32_t action) { mAction = action; }
|
||||
|
||||
inline int32_t getFlags() const { return mFlags; }
|
||||
|
||||
inline void setFlags(int32_t flags) { mFlags = flags; }
|
||||
|
||||
inline int32_t getEdgeFlags() const { return mEdgeFlags; }
|
||||
|
||||
inline void setEdgeFlags(int32_t edgeFlags) { mEdgeFlags = edgeFlags; }
|
||||
|
||||
inline int32_t getMetaState() const { return mMetaState; }
|
||||
|
||||
inline void setMetaState(int32_t metaState) { mMetaState = metaState; }
|
||||
|
||||
inline int32_t getButtonState() const { return mButtonState; }
|
||||
|
||||
inline float getXOffset() const { return mXOffset; }
|
||||
|
||||
inline float getYOffset() const { return mYOffset; }
|
||||
|
||||
inline float getXPrecision() const { return mXPrecision; }
|
||||
|
||||
inline float getYPrecision() const { return mYPrecision; }
|
||||
|
||||
inline nsecs_t getDownTime() const { return mDownTime; }
|
||||
|
||||
inline void setDownTime(nsecs_t downTime) { mDownTime = downTime; }
|
||||
|
||||
inline size_t getPointerCount() const { return mPointerProperties.size(); }
|
||||
|
||||
inline const PointerProperties* getPointerProperties(size_t pointerIndex) const {
|
||||
return &mPointerProperties[pointerIndex];
|
||||
}
|
||||
|
||||
inline int32_t getPointerId(size_t pointerIndex) const {
|
||||
return mPointerProperties[pointerIndex].id;
|
||||
}
|
||||
|
||||
inline int32_t getToolType(size_t pointerIndex) const {
|
||||
return mPointerProperties[pointerIndex].toolType;
|
||||
}
|
||||
|
||||
inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
|
||||
|
||||
const PointerCoords* getRawPointerCoords(size_t pointerIndex) const;
|
||||
|
||||
float getRawAxisValue(int32_t axis, size_t pointerIndex) const;
|
||||
|
||||
inline float getRawX(size_t pointerIndex) const {
|
||||
return getRawAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getRawY(size_t pointerIndex) const {
|
||||
return getRawAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
|
||||
}
|
||||
|
||||
float getAxisValue(int32_t axis, size_t pointerIndex) const;
|
||||
|
||||
inline float getX(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getY(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getPressure(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getSize(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getTouchMajor(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getTouchMinor(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getToolMajor(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getToolMinor(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex);
|
||||
}
|
||||
|
||||
inline float getOrientation(size_t pointerIndex) const {
|
||||
return getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex);
|
||||
}
|
||||
|
||||
inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; }
|
||||
|
||||
inline nsecs_t getHistoricalEventTime(size_t historicalIndex) const {
|
||||
return mSampleEventTimes[historicalIndex];
|
||||
}
|
||||
|
||||
const PointerCoords* getHistoricalRawPointerCoords(
|
||||
size_t pointerIndex, size_t historicalIndex) const;
|
||||
|
||||
float getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
|
||||
size_t historicalIndex) const;
|
||||
|
||||
inline float getHistoricalRawX(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalRawAxisValue(
|
||||
AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalRawY(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalRawAxisValue(
|
||||
AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
float getHistoricalAxisValue(int32_t axis, size_t pointerIndex, size_t historicalIndex) const;
|
||||
|
||||
inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalSize(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_SIZE, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalTouchMajor(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalTouchMinor(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalToolMajor(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalToolMinor(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
inline float getHistoricalOrientation(size_t pointerIndex, size_t historicalIndex) const {
|
||||
return getHistoricalAxisValue(
|
||||
AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
ssize_t findPointerIndex(int32_t pointerId) const;
|
||||
|
||||
void initialize(
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t edgeFlags,
|
||||
int32_t metaState,
|
||||
int32_t buttonState,
|
||||
float xOffset,
|
||||
float yOffset,
|
||||
float xPrecision,
|
||||
float yPrecision,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime,
|
||||
size_t pointerCount,
|
||||
const PointerProperties* pointerProperties,
|
||||
const PointerCoords* pointerCoords);
|
||||
|
||||
void copyFrom(const MotionEvent* other, bool keepHistory);
|
||||
|
||||
void addSample(
|
||||
nsecs_t eventTime,
|
||||
const PointerCoords* pointerCoords);
|
||||
|
||||
void offsetLocation(float xOffset, float yOffset);
|
||||
|
||||
void scale(float scaleFactor);
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
void transform(const SkMatrix* matrix);
|
||||
|
||||
status_t readFromParcel(Parcel* parcel);
|
||||
status_t writeToParcel(Parcel* parcel) const;
|
||||
#endif
|
||||
|
||||
static bool isTouchEvent(int32_t source, int32_t action);
|
||||
inline bool isTouchEvent() const {
|
||||
return isTouchEvent(mSource, mAction);
|
||||
}
|
||||
|
||||
// Low-level accessors.
|
||||
inline const PointerProperties* getPointerProperties() const {
|
||||
return mPointerProperties.array();
|
||||
}
|
||||
inline const nsecs_t* getSampleEventTimes() const { return mSampleEventTimes.array(); }
|
||||
inline const PointerCoords* getSamplePointerCoords() const {
|
||||
return mSamplePointerCoords.array();
|
||||
}
|
||||
|
||||
protected:
|
||||
int32_t mAction;
|
||||
int32_t mFlags;
|
||||
int32_t mEdgeFlags;
|
||||
int32_t mMetaState;
|
||||
int32_t mButtonState;
|
||||
float mXOffset;
|
||||
float mYOffset;
|
||||
float mXPrecision;
|
||||
float mYPrecision;
|
||||
nsecs_t mDownTime;
|
||||
Vector<PointerProperties> mPointerProperties;
|
||||
Vector<nsecs_t> mSampleEventTimes;
|
||||
Vector<PointerCoords> mSamplePointerCoords;
|
||||
};
|
||||
|
||||
/*
|
||||
* Input event factory.
|
||||
*/
|
||||
class InputEventFactoryInterface {
|
||||
protected:
|
||||
virtual ~InputEventFactoryInterface() { }
|
||||
|
||||
public:
|
||||
InputEventFactoryInterface() { }
|
||||
|
||||
virtual KeyEvent* createKeyEvent() = 0;
|
||||
virtual MotionEvent* createMotionEvent() = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* A simple input event factory implementation that uses a single preallocated instance
|
||||
* of each type of input event that are reused for each request.
|
||||
*/
|
||||
class PreallocatedInputEventFactory : public InputEventFactoryInterface {
|
||||
public:
|
||||
PreallocatedInputEventFactory() { }
|
||||
virtual ~PreallocatedInputEventFactory() { }
|
||||
|
||||
virtual KeyEvent* createKeyEvent() { return & mKeyEvent; }
|
||||
virtual MotionEvent* createMotionEvent() { return & mMotionEvent; }
|
||||
|
||||
private:
|
||||
KeyEvent mKeyEvent;
|
||||
MotionEvent mMotionEvent;
|
||||
};
|
||||
|
||||
/*
|
||||
* An input event factory implementation that maintains a pool of input events.
|
||||
*/
|
||||
class PooledInputEventFactory : public InputEventFactoryInterface {
|
||||
public:
|
||||
PooledInputEventFactory(size_t maxPoolSize = 20);
|
||||
virtual ~PooledInputEventFactory();
|
||||
|
||||
virtual KeyEvent* createKeyEvent();
|
||||
virtual MotionEvent* createMotionEvent();
|
||||
|
||||
void recycle(InputEvent* event);
|
||||
|
||||
private:
|
||||
const size_t mMaxPoolSize;
|
||||
|
||||
Vector<KeyEvent*> mKeyEventPool;
|
||||
Vector<MotionEvent*> mMotionEventPool;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_INPUT_H
|
156
include/input/InputDevice.h
Normal file
156
include/input/InputDevice.h
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_INPUT_DEVICE_H
|
||||
#define _LIBINPUT_INPUT_DEVICE_H
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <input/KeyCharacterMap.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
/*
|
||||
* Identifies a device.
|
||||
*/
|
||||
struct InputDeviceIdentifier {
|
||||
inline InputDeviceIdentifier() :
|
||||
bus(0), vendor(0), product(0), version(0) {
|
||||
}
|
||||
|
||||
// Information provided by the kernel.
|
||||
String8 name;
|
||||
String8 location;
|
||||
String8 uniqueId;
|
||||
uint16_t bus;
|
||||
uint16_t vendor;
|
||||
uint16_t product;
|
||||
uint16_t version;
|
||||
|
||||
// A composite input device descriptor string that uniquely identifies the device
|
||||
// even across reboots or reconnections. The value of this field is used by
|
||||
// upper layers of the input system to associate settings with individual devices.
|
||||
// It is hashed from whatever kernel provided information is available.
|
||||
// Ideally, the way this value is computed should not change between Android releases
|
||||
// because that would invalidate persistent settings that rely on it.
|
||||
String8 descriptor;
|
||||
};
|
||||
|
||||
/*
|
||||
* Describes the characteristics and capabilities of an input device.
|
||||
*/
|
||||
class InputDeviceInfo {
|
||||
public:
|
||||
InputDeviceInfo();
|
||||
InputDeviceInfo(const InputDeviceInfo& other);
|
||||
~InputDeviceInfo();
|
||||
|
||||
struct MotionRange {
|
||||
int32_t axis;
|
||||
uint32_t source;
|
||||
float min;
|
||||
float max;
|
||||
float flat;
|
||||
float fuzz;
|
||||
float resolution;
|
||||
};
|
||||
|
||||
void initialize(int32_t id, int32_t generation, const InputDeviceIdentifier& identifier,
|
||||
const String8& alias, bool isExternal);
|
||||
|
||||
inline int32_t getId() const { return mId; }
|
||||
inline int32_t getGeneration() const { return mGeneration; }
|
||||
inline const InputDeviceIdentifier& getIdentifier() const { return mIdentifier; }
|
||||
inline const String8& getAlias() const { return mAlias; }
|
||||
inline const String8& getDisplayName() const {
|
||||
return mAlias.isEmpty() ? mIdentifier.name : mAlias;
|
||||
}
|
||||
inline bool isExternal() const { return mIsExternal; }
|
||||
inline uint32_t getSources() const { return mSources; }
|
||||
|
||||
const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
|
||||
|
||||
void addSource(uint32_t source);
|
||||
void addMotionRange(int32_t axis, uint32_t source,
|
||||
float min, float max, float flat, float fuzz, float resolution);
|
||||
void addMotionRange(const MotionRange& range);
|
||||
|
||||
inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
|
||||
inline int32_t getKeyboardType() const { return mKeyboardType; }
|
||||
|
||||
inline void setKeyCharacterMap(const sp<KeyCharacterMap>& value) {
|
||||
mKeyCharacterMap = value;
|
||||
}
|
||||
|
||||
inline sp<KeyCharacterMap> getKeyCharacterMap() const {
|
||||
return mKeyCharacterMap;
|
||||
}
|
||||
|
||||
inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
|
||||
inline bool hasVibrator() const { return mHasVibrator; }
|
||||
|
||||
inline const Vector<MotionRange>& getMotionRanges() const {
|
||||
return mMotionRanges;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t mId;
|
||||
int32_t mGeneration;
|
||||
InputDeviceIdentifier mIdentifier;
|
||||
String8 mAlias;
|
||||
bool mIsExternal;
|
||||
uint32_t mSources;
|
||||
int32_t mKeyboardType;
|
||||
sp<KeyCharacterMap> mKeyCharacterMap;
|
||||
bool mHasVibrator;
|
||||
|
||||
Vector<MotionRange> mMotionRanges;
|
||||
};
|
||||
|
||||
/* Types of input device configuration files. */
|
||||
enum InputDeviceConfigurationFileType {
|
||||
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0, /* .idc file */
|
||||
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1, /* .kl file */
|
||||
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the path of an input device configuration file, if one is available.
|
||||
* Considers both system provided and user installed configuration files.
|
||||
*
|
||||
* The device identifier is used to construct several default configuration file
|
||||
* names to try based on the device name, vendor, product, and version.
|
||||
*
|
||||
* Returns an empty string if not found.
|
||||
*/
|
||||
extern String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
|
||||
const InputDeviceIdentifier& deviceIdentifier,
|
||||
InputDeviceConfigurationFileType type);
|
||||
|
||||
/*
|
||||
* Gets the path of an input device configuration file, if one is available.
|
||||
* Considers both system provided and user installed configuration files.
|
||||
*
|
||||
* The name is case-sensitive and is used to construct the filename to resolve.
|
||||
* All characters except 'a'-'z', 'A'-'Z', '0'-'9', '-', and '_' are replaced by underscores.
|
||||
*
|
||||
* Returns an empty string if not found.
|
||||
*/
|
||||
extern String8 getInputDeviceConfigurationFilePathByName(
|
||||
const String8& name, InputDeviceConfigurationFileType type);
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_INPUT_DEVICE_H
|
443
include/input/InputTransport.h
Normal file
443
include/input/InputTransport.h
Normal file
@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_INPUT_TRANSPORT_H
|
||||
#define _LIBINPUT_INPUT_TRANSPORT_H
|
||||
|
||||
/**
|
||||
* Native input transport.
|
||||
*
|
||||
* The InputChannel provides a mechanism for exchanging InputMessage structures across processes.
|
||||
*
|
||||
* The InputPublisher and InputConsumer each handle one end-point of an input channel.
|
||||
* The InputPublisher is used by the input dispatcher to send events to the application.
|
||||
* The InputConsumer is used by the application to receive events from the input dispatcher.
|
||||
*/
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <utils/BitSet.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
/*
|
||||
* Intermediate representation used to send input events and related signals.
|
||||
*/
|
||||
struct InputMessage {
|
||||
enum {
|
||||
TYPE_KEY = 1,
|
||||
TYPE_MOTION = 2,
|
||||
TYPE_FINISHED = 3,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
uint32_t type;
|
||||
uint32_t padding; // 8 byte alignment for the body that follows
|
||||
} header;
|
||||
|
||||
union Body {
|
||||
struct Key {
|
||||
uint32_t seq;
|
||||
nsecs_t eventTime;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t flags;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
int32_t metaState;
|
||||
int32_t repeatCount;
|
||||
nsecs_t downTime;
|
||||
|
||||
inline size_t size() const {
|
||||
return sizeof(Key);
|
||||
}
|
||||
} key;
|
||||
|
||||
struct Motion {
|
||||
uint32_t seq;
|
||||
nsecs_t eventTime;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
int32_t buttonState;
|
||||
int32_t edgeFlags;
|
||||
nsecs_t downTime;
|
||||
float xOffset;
|
||||
float yOffset;
|
||||
float xPrecision;
|
||||
float yPrecision;
|
||||
size_t pointerCount;
|
||||
struct Pointer {
|
||||
PointerProperties properties;
|
||||
PointerCoords coords;
|
||||
} pointers[MAX_POINTERS];
|
||||
|
||||
int32_t getActionId() const {
|
||||
uint32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
|
||||
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
return pointers[index].properties.id;
|
||||
}
|
||||
|
||||
inline size_t size() const {
|
||||
return sizeof(Motion) - sizeof(Pointer) * MAX_POINTERS
|
||||
+ sizeof(Pointer) * pointerCount;
|
||||
}
|
||||
} motion;
|
||||
|
||||
struct Finished {
|
||||
uint32_t seq;
|
||||
bool handled;
|
||||
|
||||
inline size_t size() const {
|
||||
return sizeof(Finished);
|
||||
}
|
||||
} finished;
|
||||
} body;
|
||||
|
||||
bool isValid(size_t actualSize) const;
|
||||
size_t size() const;
|
||||
};
|
||||
|
||||
/*
|
||||
* An input channel consists of a local unix domain socket used to send and receive
|
||||
* input messages across processes. Each channel has a descriptive name for debugging purposes.
|
||||
*
|
||||
* Each endpoint has its own InputChannel object that specifies its file descriptor.
|
||||
*
|
||||
* The input channel is closed when all references to it are released.
|
||||
*/
|
||||
class InputChannel : public RefBase {
|
||||
protected:
|
||||
virtual ~InputChannel();
|
||||
|
||||
public:
|
||||
InputChannel(const String8& name, int fd);
|
||||
|
||||
/* Creates a pair of input channels.
|
||||
*
|
||||
* Returns OK on success.
|
||||
*/
|
||||
static status_t openInputChannelPair(const String8& name,
|
||||
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel);
|
||||
|
||||
inline String8 getName() const { return mName; }
|
||||
inline int getFd() const { return mFd; }
|
||||
|
||||
/* Sends a message to the other endpoint.
|
||||
*
|
||||
* If the channel is full then the message is guaranteed not to have been sent at all.
|
||||
* Try again after the consumer has sent a finished signal indicating that it has
|
||||
* consumed some of the pending messages from the channel.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if the channel is full.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t sendMessage(const InputMessage* msg);
|
||||
|
||||
/* Receives a message sent by the other endpoint.
|
||||
*
|
||||
* If there is no message present, try again after poll() indicates that the fd
|
||||
* is readable.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if there is no message present.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t receiveMessage(InputMessage* msg);
|
||||
|
||||
/* Returns a new object that has a duplicate of this channel's fd. */
|
||||
sp<InputChannel> dup() const;
|
||||
|
||||
private:
|
||||
String8 mName;
|
||||
int mFd;
|
||||
};
|
||||
|
||||
/*
|
||||
* Publishes input events to an input channel.
|
||||
*/
|
||||
class InputPublisher {
|
||||
public:
|
||||
/* Creates a publisher associated with an input channel. */
|
||||
explicit InputPublisher(const sp<InputChannel>& channel);
|
||||
|
||||
/* Destroys the publisher and releases its input channel. */
|
||||
~InputPublisher();
|
||||
|
||||
/* Gets the underlying input channel. */
|
||||
inline sp<InputChannel> getChannel() { return mChannel; }
|
||||
|
||||
/* Publishes a key event to the input channel.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if the channel is full.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Returns BAD_VALUE if seq is 0.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t publishKeyEvent(
|
||||
uint32_t seq,
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t keyCode,
|
||||
int32_t scanCode,
|
||||
int32_t metaState,
|
||||
int32_t repeatCount,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime);
|
||||
|
||||
/* Publishes a motion event to the input channel.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if the channel is full.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t publishMotionEvent(
|
||||
uint32_t seq,
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t edgeFlags,
|
||||
int32_t metaState,
|
||||
int32_t buttonState,
|
||||
float xOffset,
|
||||
float yOffset,
|
||||
float xPrecision,
|
||||
float yPrecision,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime,
|
||||
size_t pointerCount,
|
||||
const PointerProperties* pointerProperties,
|
||||
const PointerCoords* pointerCoords);
|
||||
|
||||
/* Receives the finished signal from the consumer in reply to the original dispatch signal.
|
||||
* If a signal was received, returns the message sequence number,
|
||||
* and whether the consumer handled the message.
|
||||
*
|
||||
* The returned sequence number is never 0 unless the operation failed.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if there is no signal present.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t receiveFinishedSignal(uint32_t* outSeq, bool* outHandled);
|
||||
|
||||
private:
|
||||
sp<InputChannel> mChannel;
|
||||
};
|
||||
|
||||
/*
|
||||
* Consumes input events from an input channel.
|
||||
*/
|
||||
class InputConsumer {
|
||||
public:
|
||||
/* Creates a consumer associated with an input channel. */
|
||||
explicit InputConsumer(const sp<InputChannel>& channel);
|
||||
|
||||
/* Destroys the consumer and releases its input channel. */
|
||||
~InputConsumer();
|
||||
|
||||
/* Gets the underlying input channel. */
|
||||
inline sp<InputChannel> getChannel() { return mChannel; }
|
||||
|
||||
/* Consumes an input event from the input channel and copies its contents into
|
||||
* an InputEvent object created using the specified factory.
|
||||
*
|
||||
* Tries to combine a series of move events into larger batches whenever possible.
|
||||
*
|
||||
* If consumeBatches is false, then defers consuming pending batched events if it
|
||||
* is possible for additional samples to be added to them later. Call hasPendingBatch()
|
||||
* to determine whether a pending batch is available to be consumed.
|
||||
*
|
||||
* If consumeBatches is true, then events are still batched but they are consumed
|
||||
* immediately as soon as the input channel is exhausted.
|
||||
*
|
||||
* The frameTime parameter specifies the time when the current display frame started
|
||||
* rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
|
||||
*
|
||||
* The returned sequence number is never 0 unless the operation failed.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns WOULD_BLOCK if there is no event present.
|
||||
* Returns DEAD_OBJECT if the channel's peer has been closed.
|
||||
* Returns NO_MEMORY if the event could not be created.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t consume(InputEventFactoryInterface* factory, bool consumeBatches,
|
||||
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
|
||||
|
||||
/* Sends a finished signal to the publisher to inform it that the message
|
||||
* with the specified sequence number has finished being process and whether
|
||||
* the message was handled by the consumer.
|
||||
*
|
||||
* Returns OK on success.
|
||||
* Returns BAD_VALUE if seq is 0.
|
||||
* Other errors probably indicate that the channel is broken.
|
||||
*/
|
||||
status_t sendFinishedSignal(uint32_t seq, bool handled);
|
||||
|
||||
/* Returns true if there is a deferred event waiting.
|
||||
*
|
||||
* Should be called after calling consume() to determine whether the consumer
|
||||
* has a deferred event to be processed. Deferred events are somewhat special in
|
||||
* that they have already been removed from the input channel. If the input channel
|
||||
* becomes empty, the client may need to do extra work to ensure that it processes
|
||||
* the deferred event despite the fact that the input channel's file descriptor
|
||||
* is not readable.
|
||||
*
|
||||
* One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
|
||||
* This guarantees that all deferred events will be processed.
|
||||
*
|
||||
* Alternately, the caller can call hasDeferredEvent() to determine whether there is
|
||||
* a deferred event waiting and then ensure that its event loop wakes up at least
|
||||
* one more time to consume the deferred event.
|
||||
*/
|
||||
bool hasDeferredEvent() const;
|
||||
|
||||
/* Returns true if there is a pending batch.
|
||||
*
|
||||
* Should be called after calling consume() with consumeBatches == false to determine
|
||||
* whether consume() should be called again later on with consumeBatches == true.
|
||||
*/
|
||||
bool hasPendingBatch() const;
|
||||
|
||||
private:
|
||||
// True if touch resampling is enabled.
|
||||
const bool mResampleTouch;
|
||||
|
||||
// The input channel.
|
||||
sp<InputChannel> mChannel;
|
||||
|
||||
// The current input message.
|
||||
InputMessage mMsg;
|
||||
|
||||
// True if mMsg contains a valid input message that was deferred from the previous
|
||||
// call to consume and that still needs to be handled.
|
||||
bool mMsgDeferred;
|
||||
|
||||
// Batched motion events per device and source.
|
||||
struct Batch {
|
||||
Vector<InputMessage> samples;
|
||||
};
|
||||
Vector<Batch> mBatches;
|
||||
|
||||
// Touch state per device and source, only for sources of class pointer.
|
||||
struct History {
|
||||
nsecs_t eventTime;
|
||||
BitSet32 idBits;
|
||||
int32_t idToIndex[MAX_POINTER_ID + 1];
|
||||
PointerCoords pointers[MAX_POINTERS];
|
||||
|
||||
void initializeFrom(const InputMessage* msg) {
|
||||
eventTime = msg->body.motion.eventTime;
|
||||
idBits.clear();
|
||||
for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
|
||||
uint32_t id = msg->body.motion.pointers[i].properties.id;
|
||||
idBits.markBit(id);
|
||||
idToIndex[id] = i;
|
||||
pointers[i].copyFrom(msg->body.motion.pointers[i].coords);
|
||||
}
|
||||
}
|
||||
|
||||
const PointerCoords& getPointerById(uint32_t id) const {
|
||||
return pointers[idToIndex[id]];
|
||||
}
|
||||
};
|
||||
struct TouchState {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
size_t historyCurrent;
|
||||
size_t historySize;
|
||||
History history[2];
|
||||
History lastResample;
|
||||
|
||||
void initialize(int32_t deviceId, int32_t source) {
|
||||
this->deviceId = deviceId;
|
||||
this->source = source;
|
||||
historyCurrent = 0;
|
||||
historySize = 0;
|
||||
lastResample.eventTime = 0;
|
||||
lastResample.idBits.clear();
|
||||
}
|
||||
|
||||
void addHistory(const InputMessage* msg) {
|
||||
historyCurrent ^= 1;
|
||||
if (historySize < 2) {
|
||||
historySize += 1;
|
||||
}
|
||||
history[historyCurrent].initializeFrom(msg);
|
||||
}
|
||||
|
||||
const History* getHistory(size_t index) const {
|
||||
return &history[(historyCurrent + index) & 1];
|
||||
}
|
||||
};
|
||||
Vector<TouchState> mTouchStates;
|
||||
|
||||
// Chain of batched sequence numbers. When multiple input messages are combined into
|
||||
// a batch, we append a record here that associates the last sequence number in the
|
||||
// batch with the previous one. When the finished signal is sent, we traverse the
|
||||
// chain to individually finish all input messages that were part of the batch.
|
||||
struct SeqChain {
|
||||
uint32_t seq; // sequence number of batched input message
|
||||
uint32_t chain; // sequence number of previous batched input message
|
||||
};
|
||||
Vector<SeqChain> mSeqChains;
|
||||
|
||||
status_t consumeBatch(InputEventFactoryInterface* factory,
|
||||
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
|
||||
status_t consumeSamples(InputEventFactoryInterface* factory,
|
||||
Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
|
||||
|
||||
void updateTouchState(InputMessage* msg);
|
||||
void rewriteMessage(const TouchState& state, InputMessage* msg);
|
||||
void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
|
||||
const InputMessage *next);
|
||||
|
||||
ssize_t findBatch(int32_t deviceId, int32_t source) const;
|
||||
ssize_t findTouchState(int32_t deviceId, int32_t source) const;
|
||||
|
||||
status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
|
||||
|
||||
static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
|
||||
static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
|
||||
static void addSample(MotionEvent* event, const InputMessage* msg);
|
||||
static bool canAddSample(const Batch& batch, const InputMessage* msg);
|
||||
static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
|
||||
static bool shouldResampleTool(int32_t toolType);
|
||||
|
||||
static bool isTouchResamplingEnabled();
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_INPUT_TRANSPORT_H
|
257
include/input/KeyCharacterMap.h
Normal file
257
include/input/KeyCharacterMap.h
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_KEY_CHARACTER_MAP_H
|
||||
#define _LIBINPUT_KEY_CHARACTER_MAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if HAVE_ANDROID_OS
|
||||
#include <binder/IBinder.h>
|
||||
#endif
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
#include <utils/Tokenizer.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Unicode.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Describes a mapping from Android key codes to characters.
|
||||
* Also specifies other functions of the keyboard such as the keyboard type
|
||||
* and key modifier semantics.
|
||||
*
|
||||
* This object is immutable after it has been loaded.
|
||||
*/
|
||||
class KeyCharacterMap : public RefBase {
|
||||
public:
|
||||
enum KeyboardType {
|
||||
KEYBOARD_TYPE_UNKNOWN = 0,
|
||||
KEYBOARD_TYPE_NUMERIC = 1,
|
||||
KEYBOARD_TYPE_PREDICTIVE = 2,
|
||||
KEYBOARD_TYPE_ALPHA = 3,
|
||||
KEYBOARD_TYPE_FULL = 4,
|
||||
KEYBOARD_TYPE_SPECIAL_FUNCTION = 5,
|
||||
KEYBOARD_TYPE_OVERLAY = 6,
|
||||
};
|
||||
|
||||
enum Format {
|
||||
// Base keyboard layout, may contain device-specific options, such as "type" declaration.
|
||||
FORMAT_BASE = 0,
|
||||
// Overlay keyboard layout, more restrictive, may be published by applications,
|
||||
// cannot override device-specific options.
|
||||
FORMAT_OVERLAY = 1,
|
||||
// Either base or overlay layout ok.
|
||||
FORMAT_ANY = 2,
|
||||
};
|
||||
|
||||
// Substitute key code and meta state for fallback action.
|
||||
struct FallbackAction {
|
||||
int32_t keyCode;
|
||||
int32_t metaState;
|
||||
};
|
||||
|
||||
/* Loads a key character map from a file. */
|
||||
static status_t load(const String8& filename, Format format, sp<KeyCharacterMap>* outMap);
|
||||
|
||||
/* Loads a key character map from its string contents. */
|
||||
static status_t loadContents(const String8& filename,
|
||||
const char* contents, Format format, sp<KeyCharacterMap>* outMap);
|
||||
|
||||
/* Combines a base key character map and an overlay. */
|
||||
static sp<KeyCharacterMap> combine(const sp<KeyCharacterMap>& base,
|
||||
const sp<KeyCharacterMap>& overlay);
|
||||
|
||||
/* Returns an empty key character map. */
|
||||
static sp<KeyCharacterMap> empty();
|
||||
|
||||
/* Gets the keyboard type. */
|
||||
int32_t getKeyboardType() const;
|
||||
|
||||
/* Gets the primary character for this key as in the label physically printed on it.
|
||||
* Returns 0 if none (eg. for non-printing keys). */
|
||||
char16_t getDisplayLabel(int32_t keyCode) const;
|
||||
|
||||
/* Gets the Unicode character for the number or symbol generated by the key
|
||||
* when the keyboard is used as a dialing pad.
|
||||
* Returns 0 if no number or symbol is generated.
|
||||
*/
|
||||
char16_t getNumber(int32_t keyCode) const;
|
||||
|
||||
/* Gets the Unicode character generated by the key and meta key modifiers.
|
||||
* Returns 0 if no character is generated.
|
||||
*/
|
||||
char16_t getCharacter(int32_t keyCode, int32_t metaState) const;
|
||||
|
||||
/* Gets the fallback action to use by default if the application does not
|
||||
* handle the specified key.
|
||||
* Returns true if an action was available, false if none.
|
||||
*/
|
||||
bool getFallbackAction(int32_t keyCode, int32_t metaState,
|
||||
FallbackAction* outFallbackAction) const;
|
||||
|
||||
/* Gets the first matching Unicode character that can be generated by the key,
|
||||
* preferring the one with the specified meta key modifiers.
|
||||
* Returns 0 if no matching character is generated.
|
||||
*/
|
||||
char16_t getMatch(int32_t keyCode, const char16_t* chars,
|
||||
size_t numChars, int32_t metaState) const;
|
||||
|
||||
/* Gets a sequence of key events that could plausibly generate the specified
|
||||
* character sequence. Returns false if some of the characters cannot be generated.
|
||||
*/
|
||||
bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
|
||||
Vector<KeyEvent>& outEvents) const;
|
||||
|
||||
/* Maps a scan code and usage code to a key code, in case this key map overrides
|
||||
* the mapping in some way. */
|
||||
status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const;
|
||||
|
||||
#if HAVE_ANDROID_OS
|
||||
/* Reads a key map from a parcel. */
|
||||
static sp<KeyCharacterMap> readFromParcel(Parcel* parcel);
|
||||
|
||||
/* Writes a key map to a parcel. */
|
||||
void writeToParcel(Parcel* parcel) const;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual ~KeyCharacterMap();
|
||||
|
||||
private:
|
||||
struct Behavior {
|
||||
Behavior();
|
||||
Behavior(const Behavior& other);
|
||||
|
||||
/* The next behavior in the list, or NULL if none. */
|
||||
Behavior* next;
|
||||
|
||||
/* The meta key modifiers for this behavior. */
|
||||
int32_t metaState;
|
||||
|
||||
/* The character to insert. */
|
||||
char16_t character;
|
||||
|
||||
/* The fallback keycode if the key is not handled. */
|
||||
int32_t fallbackKeyCode;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
Key();
|
||||
Key(const Key& other);
|
||||
~Key();
|
||||
|
||||
/* The single character label printed on the key, or 0 if none. */
|
||||
char16_t label;
|
||||
|
||||
/* The number or symbol character generated by the key, or 0 if none. */
|
||||
char16_t number;
|
||||
|
||||
/* The list of key behaviors sorted from most specific to least specific
|
||||
* meta key binding. */
|
||||
Behavior* firstBehavior;
|
||||
};
|
||||
|
||||
class Parser {
|
||||
enum State {
|
||||
STATE_TOP = 0,
|
||||
STATE_KEY = 1,
|
||||
};
|
||||
|
||||
enum {
|
||||
PROPERTY_LABEL = 1,
|
||||
PROPERTY_NUMBER = 2,
|
||||
PROPERTY_META = 3,
|
||||
};
|
||||
|
||||
struct Property {
|
||||
inline Property(int32_t property = 0, int32_t metaState = 0) :
|
||||
property(property), metaState(metaState) { }
|
||||
|
||||
int32_t property;
|
||||
int32_t metaState;
|
||||
};
|
||||
|
||||
KeyCharacterMap* mMap;
|
||||
Tokenizer* mTokenizer;
|
||||
Format mFormat;
|
||||
State mState;
|
||||
int32_t mKeyCode;
|
||||
|
||||
public:
|
||||
Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format);
|
||||
~Parser();
|
||||
status_t parse();
|
||||
|
||||
private:
|
||||
status_t parseType();
|
||||
status_t parseMap();
|
||||
status_t parseMapKey();
|
||||
status_t parseKey();
|
||||
status_t parseKeyProperty();
|
||||
status_t finishKey(Key* key);
|
||||
status_t parseModifier(const String8& token, int32_t* outMetaState);
|
||||
status_t parseCharacterLiteral(char16_t* outCharacter);
|
||||
};
|
||||
|
||||
static sp<KeyCharacterMap> sEmpty;
|
||||
|
||||
KeyedVector<int32_t, Key*> mKeys;
|
||||
int mType;
|
||||
|
||||
KeyedVector<int32_t, int32_t> mKeysByScanCode;
|
||||
KeyedVector<int32_t, int32_t> mKeysByUsageCode;
|
||||
|
||||
KeyCharacterMap();
|
||||
KeyCharacterMap(const KeyCharacterMap& other);
|
||||
|
||||
bool getKey(int32_t keyCode, const Key** outKey) const;
|
||||
bool getKeyBehavior(int32_t keyCode, int32_t metaState,
|
||||
const Key** outKey, const Behavior** outBehavior) const;
|
||||
static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState);
|
||||
|
||||
bool findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const;
|
||||
|
||||
static status_t load(Tokenizer* tokenizer, Format format, sp<KeyCharacterMap>* outMap);
|
||||
|
||||
static void addKey(Vector<KeyEvent>& outEvents,
|
||||
int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time);
|
||||
static void addMetaKeys(Vector<KeyEvent>& outEvents,
|
||||
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
||||
int32_t* currentMetaState);
|
||||
static bool addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
|
||||
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
||||
int32_t keyCode, int32_t keyMetaState,
|
||||
int32_t* currentMetaState);
|
||||
static void addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
|
||||
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
||||
int32_t leftKeyCode, int32_t leftKeyMetaState,
|
||||
int32_t rightKeyCode, int32_t rightKeyMetaState,
|
||||
int32_t eitherKeyMetaState,
|
||||
int32_t* currentMetaState);
|
||||
static void addLockedMetaKey(Vector<KeyEvent>& outEvents,
|
||||
int32_t deviceId, int32_t metaState, nsecs_t time,
|
||||
int32_t keyCode, int32_t keyMetaState,
|
||||
int32_t* currentMetaState);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_KEY_CHARACTER_MAP_H
|
107
include/input/KeyLayoutMap.h
Normal file
107
include/input/KeyLayoutMap.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_KEY_LAYOUT_MAP_H
|
||||
#define _LIBINPUT_KEY_LAYOUT_MAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
#include <utils/Tokenizer.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
struct AxisInfo {
|
||||
enum Mode {
|
||||
// Axis value is reported directly.
|
||||
MODE_NORMAL = 0,
|
||||
// Axis value should be inverted before reporting.
|
||||
MODE_INVERT = 1,
|
||||
// Axis value should be split into two axes
|
||||
MODE_SPLIT = 2,
|
||||
};
|
||||
|
||||
// Axis mode.
|
||||
Mode mode;
|
||||
|
||||
// Axis id.
|
||||
// When split, this is the axis used for values smaller than the split position.
|
||||
int32_t axis;
|
||||
|
||||
// When split, this is the axis used for values after higher than the split position.
|
||||
int32_t highAxis;
|
||||
|
||||
// The split value, or 0 if not split.
|
||||
int32_t splitValue;
|
||||
|
||||
// The flat value, or -1 if none.
|
||||
int32_t flatOverride;
|
||||
|
||||
AxisInfo() : mode(MODE_NORMAL), axis(-1), highAxis(-1), splitValue(0), flatOverride(-1) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes a mapping from keyboard scan codes and joystick axes to Android key codes and axes.
|
||||
*
|
||||
* This object is immutable after it has been loaded.
|
||||
*/
|
||||
class KeyLayoutMap : public RefBase {
|
||||
public:
|
||||
static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
|
||||
|
||||
status_t mapKey(int32_t scanCode, int32_t usageCode,
|
||||
int32_t* outKeyCode, uint32_t* outFlags) const;
|
||||
status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
|
||||
|
||||
status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
|
||||
|
||||
protected:
|
||||
virtual ~KeyLayoutMap();
|
||||
|
||||
private:
|
||||
struct Key {
|
||||
int32_t keyCode;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
KeyedVector<int32_t, Key> mKeysByScanCode;
|
||||
KeyedVector<int32_t, Key> mKeysByUsageCode;
|
||||
KeyedVector<int32_t, AxisInfo> mAxes;
|
||||
|
||||
KeyLayoutMap();
|
||||
|
||||
const Key* getKey(int32_t scanCode, int32_t usageCode) const;
|
||||
|
||||
class Parser {
|
||||
KeyLayoutMap* mMap;
|
||||
Tokenizer* mTokenizer;
|
||||
|
||||
public:
|
||||
Parser(KeyLayoutMap* map, Tokenizer* tokenizer);
|
||||
~Parser();
|
||||
status_t parse();
|
||||
|
||||
private:
|
||||
status_t parseKey();
|
||||
status_t parseAxis();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_KEY_LAYOUT_MAP_H
|
120
include/input/Keyboard.h
Normal file
120
include/input/Keyboard.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_KEYBOARD_H
|
||||
#define _LIBINPUT_KEYBOARD_H
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <input/InputDevice.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/PropertyMap.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
enum {
|
||||
/* Device id of the built in keyboard. */
|
||||
DEVICE_ID_BUILT_IN_KEYBOARD = 0,
|
||||
|
||||
/* Device id of a generic virtual keyboard with a full layout that can be used
|
||||
* to synthesize key events. */
|
||||
DEVICE_ID_VIRTUAL_KEYBOARD = -1,
|
||||
};
|
||||
|
||||
class KeyLayoutMap;
|
||||
class KeyCharacterMap;
|
||||
|
||||
/**
|
||||
* Loads the key layout map and key character map for a keyboard device.
|
||||
*/
|
||||
class KeyMap {
|
||||
public:
|
||||
String8 keyLayoutFile;
|
||||
sp<KeyLayoutMap> keyLayoutMap;
|
||||
|
||||
String8 keyCharacterMapFile;
|
||||
sp<KeyCharacterMap> keyCharacterMap;
|
||||
|
||||
KeyMap();
|
||||
~KeyMap();
|
||||
|
||||
status_t load(const InputDeviceIdentifier& deviceIdenfier,
|
||||
const PropertyMap* deviceConfiguration);
|
||||
|
||||
inline bool haveKeyLayout() const {
|
||||
return !keyLayoutFile.isEmpty();
|
||||
}
|
||||
|
||||
inline bool haveKeyCharacterMap() const {
|
||||
return !keyCharacterMapFile.isEmpty();
|
||||
}
|
||||
|
||||
inline bool isComplete() const {
|
||||
return haveKeyLayout() && haveKeyCharacterMap();
|
||||
}
|
||||
|
||||
private:
|
||||
bool probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
|
||||
status_t loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
|
||||
status_t loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& name);
|
||||
String8 getPath(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& name, InputDeviceConfigurationFileType type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the keyboard is eligible for use as a built-in keyboard.
|
||||
*/
|
||||
extern bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const PropertyMap* deviceConfiguration, const KeyMap* keyMap);
|
||||
|
||||
/**
|
||||
* Gets a key code by its short form label, eg. "HOME".
|
||||
* Returns 0 if unknown.
|
||||
*/
|
||||
extern int32_t getKeyCodeByLabel(const char* label);
|
||||
|
||||
/**
|
||||
* Gets a key flag by its short form label, eg. "WAKE".
|
||||
* Returns 0 if unknown.
|
||||
*/
|
||||
extern uint32_t getKeyFlagByLabel(const char* label);
|
||||
|
||||
/**
|
||||
* Gets a axis by its short form label, eg. "X".
|
||||
* Returns -1 if unknown.
|
||||
*/
|
||||
extern int32_t getAxisByLabel(const char* label);
|
||||
|
||||
/**
|
||||
* Gets a axis label by its id.
|
||||
* Returns NULL if unknown.
|
||||
*/
|
||||
extern const char* getAxisLabel(int32_t axisId);
|
||||
|
||||
/**
|
||||
* Updates a meta state field when a key is pressed or released.
|
||||
*/
|
||||
extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
|
||||
|
||||
/**
|
||||
* Returns true if a key is a meta key like ALT or CAPS_LOCK.
|
||||
*/
|
||||
extern bool isMetaKey(int32_t keyCode);
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_KEYBOARD_H
|
321
include/input/KeycodeLabels.h
Normal file
321
include/input/KeycodeLabels.h
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_KEYCODE_LABELS_H
|
||||
#define _LIBINPUT_KEYCODE_LABELS_H
|
||||
|
||||
#include <android/keycodes.h>
|
||||
|
||||
struct KeycodeLabel {
|
||||
const char *literal;
|
||||
int value;
|
||||
};
|
||||
|
||||
static const KeycodeLabel KEYCODES[] = {
|
||||
{ "SOFT_LEFT", 1 },
|
||||
{ "SOFT_RIGHT", 2 },
|
||||
{ "HOME", 3 },
|
||||
{ "BACK", 4 },
|
||||
{ "CALL", 5 },
|
||||
{ "ENDCALL", 6 },
|
||||
{ "0", 7 },
|
||||
{ "1", 8 },
|
||||
{ "2", 9 },
|
||||
{ "3", 10 },
|
||||
{ "4", 11 },
|
||||
{ "5", 12 },
|
||||
{ "6", 13 },
|
||||
{ "7", 14 },
|
||||
{ "8", 15 },
|
||||
{ "9", 16 },
|
||||
{ "STAR", 17 },
|
||||
{ "POUND", 18 },
|
||||
{ "DPAD_UP", 19 },
|
||||
{ "DPAD_DOWN", 20 },
|
||||
{ "DPAD_LEFT", 21 },
|
||||
{ "DPAD_RIGHT", 22 },
|
||||
{ "DPAD_CENTER", 23 },
|
||||
{ "VOLUME_UP", 24 },
|
||||
{ "VOLUME_DOWN", 25 },
|
||||
{ "POWER", 26 },
|
||||
{ "CAMERA", 27 },
|
||||
{ "CLEAR", 28 },
|
||||
{ "A", 29 },
|
||||
{ "B", 30 },
|
||||
{ "C", 31 },
|
||||
{ "D", 32 },
|
||||
{ "E", 33 },
|
||||
{ "F", 34 },
|
||||
{ "G", 35 },
|
||||
{ "H", 36 },
|
||||
{ "I", 37 },
|
||||
{ "J", 38 },
|
||||
{ "K", 39 },
|
||||
{ "L", 40 },
|
||||
{ "M", 41 },
|
||||
{ "N", 42 },
|
||||
{ "O", 43 },
|
||||
{ "P", 44 },
|
||||
{ "Q", 45 },
|
||||
{ "R", 46 },
|
||||
{ "S", 47 },
|
||||
{ "T", 48 },
|
||||
{ "U", 49 },
|
||||
{ "V", 50 },
|
||||
{ "W", 51 },
|
||||
{ "X", 52 },
|
||||
{ "Y", 53 },
|
||||
{ "Z", 54 },
|
||||
{ "COMMA", 55 },
|
||||
{ "PERIOD", 56 },
|
||||
{ "ALT_LEFT", 57 },
|
||||
{ "ALT_RIGHT", 58 },
|
||||
{ "SHIFT_LEFT", 59 },
|
||||
{ "SHIFT_RIGHT", 60 },
|
||||
{ "TAB", 61 },
|
||||
{ "SPACE", 62 },
|
||||
{ "SYM", 63 },
|
||||
{ "EXPLORER", 64 },
|
||||
{ "ENVELOPE", 65 },
|
||||
{ "ENTER", 66 },
|
||||
{ "DEL", 67 },
|
||||
{ "GRAVE", 68 },
|
||||
{ "MINUS", 69 },
|
||||
{ "EQUALS", 70 },
|
||||
{ "LEFT_BRACKET", 71 },
|
||||
{ "RIGHT_BRACKET", 72 },
|
||||
{ "BACKSLASH", 73 },
|
||||
{ "SEMICOLON", 74 },
|
||||
{ "APOSTROPHE", 75 },
|
||||
{ "SLASH", 76 },
|
||||
{ "AT", 77 },
|
||||
{ "NUM", 78 },
|
||||
{ "HEADSETHOOK", 79 },
|
||||
{ "FOCUS", 80 },
|
||||
{ "PLUS", 81 },
|
||||
{ "MENU", 82 },
|
||||
{ "NOTIFICATION", 83 },
|
||||
{ "SEARCH", 84 },
|
||||
{ "MEDIA_PLAY_PAUSE", 85 },
|
||||
{ "MEDIA_STOP", 86 },
|
||||
{ "MEDIA_NEXT", 87 },
|
||||
{ "MEDIA_PREVIOUS", 88 },
|
||||
{ "MEDIA_REWIND", 89 },
|
||||
{ "MEDIA_FAST_FORWARD", 90 },
|
||||
{ "MUTE", 91 },
|
||||
{ "PAGE_UP", 92 },
|
||||
{ "PAGE_DOWN", 93 },
|
||||
{ "PICTSYMBOLS", 94 },
|
||||
{ "SWITCH_CHARSET", 95 },
|
||||
{ "BUTTON_A", 96 },
|
||||
{ "BUTTON_B", 97 },
|
||||
{ "BUTTON_C", 98 },
|
||||
{ "BUTTON_X", 99 },
|
||||
{ "BUTTON_Y", 100 },
|
||||
{ "BUTTON_Z", 101 },
|
||||
{ "BUTTON_L1", 102 },
|
||||
{ "BUTTON_R1", 103 },
|
||||
{ "BUTTON_L2", 104 },
|
||||
{ "BUTTON_R2", 105 },
|
||||
{ "BUTTON_THUMBL", 106 },
|
||||
{ "BUTTON_THUMBR", 107 },
|
||||
{ "BUTTON_START", 108 },
|
||||
{ "BUTTON_SELECT", 109 },
|
||||
{ "BUTTON_MODE", 110 },
|
||||
{ "ESCAPE", 111 },
|
||||
{ "FORWARD_DEL", 112 },
|
||||
{ "CTRL_LEFT", 113 },
|
||||
{ "CTRL_RIGHT", 114 },
|
||||
{ "CAPS_LOCK", 115 },
|
||||
{ "SCROLL_LOCK", 116 },
|
||||
{ "META_LEFT", 117 },
|
||||
{ "META_RIGHT", 118 },
|
||||
{ "FUNCTION", 119 },
|
||||
{ "SYSRQ", 120 },
|
||||
{ "BREAK", 121 },
|
||||
{ "MOVE_HOME", 122 },
|
||||
{ "MOVE_END", 123 },
|
||||
{ "INSERT", 124 },
|
||||
{ "FORWARD", 125 },
|
||||
{ "MEDIA_PLAY", 126 },
|
||||
{ "MEDIA_PAUSE", 127 },
|
||||
{ "MEDIA_CLOSE", 128 },
|
||||
{ "MEDIA_EJECT", 129 },
|
||||
{ "MEDIA_RECORD", 130 },
|
||||
{ "F1", 131 },
|
||||
{ "F2", 132 },
|
||||
{ "F3", 133 },
|
||||
{ "F4", 134 },
|
||||
{ "F5", 135 },
|
||||
{ "F6", 136 },
|
||||
{ "F7", 137 },
|
||||
{ "F8", 138 },
|
||||
{ "F9", 139 },
|
||||
{ "F10", 140 },
|
||||
{ "F11", 141 },
|
||||
{ "F12", 142 },
|
||||
{ "NUM_LOCK", 143 },
|
||||
{ "NUMPAD_0", 144 },
|
||||
{ "NUMPAD_1", 145 },
|
||||
{ "NUMPAD_2", 146 },
|
||||
{ "NUMPAD_3", 147 },
|
||||
{ "NUMPAD_4", 148 },
|
||||
{ "NUMPAD_5", 149 },
|
||||
{ "NUMPAD_6", 150 },
|
||||
{ "NUMPAD_7", 151 },
|
||||
{ "NUMPAD_8", 152 },
|
||||
{ "NUMPAD_9", 153 },
|
||||
{ "NUMPAD_DIVIDE", 154 },
|
||||
{ "NUMPAD_MULTIPLY", 155 },
|
||||
{ "NUMPAD_SUBTRACT", 156 },
|
||||
{ "NUMPAD_ADD", 157 },
|
||||
{ "NUMPAD_DOT", 158 },
|
||||
{ "NUMPAD_COMMA", 159 },
|
||||
{ "NUMPAD_ENTER", 160 },
|
||||
{ "NUMPAD_EQUALS", 161 },
|
||||
{ "NUMPAD_LEFT_PAREN", 162 },
|
||||
{ "NUMPAD_RIGHT_PAREN", 163 },
|
||||
{ "VOLUME_MUTE", 164 },
|
||||
{ "INFO", 165 },
|
||||
{ "CHANNEL_UP", 166 },
|
||||
{ "CHANNEL_DOWN", 167 },
|
||||
{ "ZOOM_IN", 168 },
|
||||
{ "ZOOM_OUT", 169 },
|
||||
{ "TV", 170 },
|
||||
{ "WINDOW", 171 },
|
||||
{ "GUIDE", 172 },
|
||||
{ "DVR", 173 },
|
||||
{ "BOOKMARK", 174 },
|
||||
{ "CAPTIONS", 175 },
|
||||
{ "SETTINGS", 176 },
|
||||
{ "TV_POWER", 177 },
|
||||
{ "TV_INPUT", 178 },
|
||||
{ "STB_POWER", 179 },
|
||||
{ "STB_INPUT", 180 },
|
||||
{ "AVR_POWER", 181 },
|
||||
{ "AVR_INPUT", 182 },
|
||||
{ "PROG_RED", 183 },
|
||||
{ "PROG_GREEN", 184 },
|
||||
{ "PROG_YELLOW", 185 },
|
||||
{ "PROG_BLUE", 186 },
|
||||
{ "APP_SWITCH", 187 },
|
||||
{ "BUTTON_1", 188 },
|
||||
{ "BUTTON_2", 189 },
|
||||
{ "BUTTON_3", 190 },
|
||||
{ "BUTTON_4", 191 },
|
||||
{ "BUTTON_5", 192 },
|
||||
{ "BUTTON_6", 193 },
|
||||
{ "BUTTON_7", 194 },
|
||||
{ "BUTTON_8", 195 },
|
||||
{ "BUTTON_9", 196 },
|
||||
{ "BUTTON_10", 197 },
|
||||
{ "BUTTON_11", 198 },
|
||||
{ "BUTTON_12", 199 },
|
||||
{ "BUTTON_13", 200 },
|
||||
{ "BUTTON_14", 201 },
|
||||
{ "BUTTON_15", 202 },
|
||||
{ "BUTTON_16", 203 },
|
||||
{ "LANGUAGE_SWITCH", 204 },
|
||||
{ "MANNER_MODE", 205 },
|
||||
{ "3D_MODE", 206 },
|
||||
{ "CONTACTS", 207 },
|
||||
{ "CALENDAR", 208 },
|
||||
{ "MUSIC", 209 },
|
||||
{ "CALCULATOR", 210 },
|
||||
{ "ZENKAKU_HANKAKU", 211 },
|
||||
{ "EISU", 212 },
|
||||
{ "MUHENKAN", 213 },
|
||||
{ "HENKAN", 214 },
|
||||
{ "KATAKANA_HIRAGANA", 215 },
|
||||
{ "YEN", 216 },
|
||||
{ "RO", 217 },
|
||||
{ "KANA", 218 },
|
||||
{ "ASSIST", 219 },
|
||||
{ "BRIGHTNESS_DOWN", 220 },
|
||||
{ "BRIGHTNESS_UP", 221 },
|
||||
|
||||
// NOTE: If you add a new keycode here you must also add it to several other files.
|
||||
// Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
|
||||
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
// NOTE: If you edit these flags, also edit policy flags in Input.h.
|
||||
static const KeycodeLabel FLAGS[] = {
|
||||
{ "WAKE", 0x00000001 },
|
||||
{ "WAKE_DROPPED", 0x00000002 },
|
||||
{ "SHIFT", 0x00000004 },
|
||||
{ "CAPS_LOCK", 0x00000008 },
|
||||
{ "ALT", 0x00000010 },
|
||||
{ "ALT_GR", 0x00000020 },
|
||||
{ "MENU", 0x00000040 },
|
||||
{ "LAUNCHER", 0x00000080 },
|
||||
{ "VIRTUAL", 0x00000100 },
|
||||
{ "FUNCTION", 0x00000200 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static const KeycodeLabel AXES[] = {
|
||||
{ "X", 0 },
|
||||
{ "Y", 1 },
|
||||
{ "PRESSURE", 2 },
|
||||
{ "SIZE", 3 },
|
||||
{ "TOUCH_MAJOR", 4 },
|
||||
{ "TOUCH_MINOR", 5 },
|
||||
{ "TOOL_MAJOR", 6 },
|
||||
{ "TOOL_MINOR", 7 },
|
||||
{ "ORIENTATION", 8 },
|
||||
{ "VSCROLL", 9 },
|
||||
{ "HSCROLL", 10 },
|
||||
{ "Z", 11 },
|
||||
{ "RX", 12 },
|
||||
{ "RY", 13 },
|
||||
{ "RZ", 14 },
|
||||
{ "HAT_X", 15 },
|
||||
{ "HAT_Y", 16 },
|
||||
{ "LTRIGGER", 17 },
|
||||
{ "RTRIGGER", 18 },
|
||||
{ "THROTTLE", 19 },
|
||||
{ "RUDDER", 20 },
|
||||
{ "WHEEL", 21 },
|
||||
{ "GAS", 22 },
|
||||
{ "BRAKE", 23 },
|
||||
{ "DISTANCE", 24 },
|
||||
{ "TILT", 25 },
|
||||
{ "GENERIC_1", 32 },
|
||||
{ "GENERIC_2", 33 },
|
||||
{ "GENERIC_3", 34 },
|
||||
{ "GENERIC_4", 35 },
|
||||
{ "GENERIC_5", 36 },
|
||||
{ "GENERIC_6", 37 },
|
||||
{ "GENERIC_7", 38 },
|
||||
{ "GENERIC_8", 39 },
|
||||
{ "GENERIC_9", 40 },
|
||||
{ "GENERIC_10", 41 },
|
||||
{ "GENERIC_11", 42 },
|
||||
{ "GENERIC_12", 43 },
|
||||
{ "GENERIC_13", 44 },
|
||||
{ "GENERIC_14", 45 },
|
||||
{ "GENERIC_15", 46 },
|
||||
{ "GENERIC_16", 47 },
|
||||
|
||||
// NOTE: If you add a new axis here you must also add it to several other files.
|
||||
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
|
||||
|
||||
{ NULL, -1 }
|
||||
};
|
||||
|
||||
#endif // _LIBINPUT_KEYCODE_LABELS_H
|
107
include/input/VelocityControl.h
Normal file
107
include/input/VelocityControl.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_VELOCITY_CONTROL_H
|
||||
#define _LIBINPUT_VELOCITY_CONTROL_H
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <input/VelocityTracker.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
/*
|
||||
* Specifies parameters that govern pointer or wheel acceleration.
|
||||
*/
|
||||
struct VelocityControlParameters {
|
||||
// A scale factor that is multiplied with the raw velocity deltas
|
||||
// prior to applying any other velocity control factors. The scale
|
||||
// factor should be used to adapt the input device resolution
|
||||
// (eg. counts per inch) to the output device resolution (eg. pixels per inch).
|
||||
//
|
||||
// Must be a positive value.
|
||||
// Default is 1.0 (no scaling).
|
||||
float scale;
|
||||
|
||||
// The scaled speed at which acceleration begins to be applied.
|
||||
// This value establishes the upper bound of a low speed regime for
|
||||
// small precise motions that are performed without any acceleration.
|
||||
//
|
||||
// Must be a non-negative value.
|
||||
// Default is 0.0 (no low threshold).
|
||||
float lowThreshold;
|
||||
|
||||
// The scaled speed at which maximum acceleration is applied.
|
||||
// The difference between highThreshold and lowThreshold controls
|
||||
// the range of speeds over which the acceleration factor is interpolated.
|
||||
// The wider the range, the smoother the acceleration.
|
||||
//
|
||||
// Must be a non-negative value greater than or equal to lowThreshold.
|
||||
// Default is 0.0 (no high threshold).
|
||||
float highThreshold;
|
||||
|
||||
// The acceleration factor.
|
||||
// When the speed is above the low speed threshold, the velocity will scaled
|
||||
// by an interpolated value between 1.0 and this amount.
|
||||
//
|
||||
// Must be a positive greater than or equal to 1.0.
|
||||
// Default is 1.0 (no acceleration).
|
||||
float acceleration;
|
||||
|
||||
VelocityControlParameters() :
|
||||
scale(1.0f), lowThreshold(0.0f), highThreshold(0.0f), acceleration(1.0f) {
|
||||
}
|
||||
|
||||
VelocityControlParameters(float scale, float lowThreshold,
|
||||
float highThreshold, float acceleration) :
|
||||
scale(scale), lowThreshold(lowThreshold),
|
||||
highThreshold(highThreshold), acceleration(acceleration) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Implements mouse pointer and wheel speed control and acceleration.
|
||||
*/
|
||||
class VelocityControl {
|
||||
public:
|
||||
VelocityControl();
|
||||
|
||||
/* Sets the various parameters. */
|
||||
void setParameters(const VelocityControlParameters& parameters);
|
||||
|
||||
/* Resets the current movement counters to zero.
|
||||
* This has the effect of nullifying any acceleration. */
|
||||
void reset();
|
||||
|
||||
/* Translates a raw movement delta into an appropriately
|
||||
* scaled / accelerated delta based on the current velocity. */
|
||||
void move(nsecs_t eventTime, float* deltaX, float* deltaY);
|
||||
|
||||
private:
|
||||
// If no movements are received within this amount of time,
|
||||
// we assume the movement has stopped and reset the movement counters.
|
||||
static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms
|
||||
|
||||
VelocityControlParameters mParameters;
|
||||
|
||||
nsecs_t mLastMovementTime;
|
||||
VelocityTracker::Position mRawPosition;
|
||||
VelocityTracker mVelocityTracker;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_VELOCITY_CONTROL_H
|
269
include/input/VelocityTracker.h
Normal file
269
include/input/VelocityTracker.h
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_VELOCITY_TRACKER_H
|
||||
#define _LIBINPUT_VELOCITY_TRACKER_H
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/BitSet.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class VelocityTrackerStrategy;
|
||||
|
||||
/*
|
||||
* Calculates the velocity of pointer movements over time.
|
||||
*/
|
||||
class VelocityTracker {
|
||||
public:
|
||||
struct Position {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct Estimator {
|
||||
static const size_t MAX_DEGREE = 4;
|
||||
|
||||
// Estimator time base.
|
||||
nsecs_t time;
|
||||
|
||||
// Polynomial coefficients describing motion in X and Y.
|
||||
float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
|
||||
|
||||
// Polynomial degree (number of coefficients), or zero if no information is
|
||||
// available.
|
||||
uint32_t degree;
|
||||
|
||||
// Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
|
||||
float confidence;
|
||||
|
||||
inline void clear() {
|
||||
time = 0;
|
||||
degree = 0;
|
||||
confidence = 0;
|
||||
for (size_t i = 0; i <= MAX_DEGREE; i++) {
|
||||
xCoeff[i] = 0;
|
||||
yCoeff[i] = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a velocity tracker using the specified strategy.
|
||||
// If strategy is NULL, uses the default strategy for the platform.
|
||||
VelocityTracker(const char* strategy = NULL);
|
||||
|
||||
~VelocityTracker();
|
||||
|
||||
// Resets the velocity tracker state.
|
||||
void clear();
|
||||
|
||||
// Resets the velocity tracker state for specific pointers.
|
||||
// Call this method when some pointers have changed and may be reusing
|
||||
// an id that was assigned to a different pointer earlier.
|
||||
void clearPointers(BitSet32 idBits);
|
||||
|
||||
// Adds movement information for a set of pointers.
|
||||
// The idBits bitfield specifies the pointer ids of the pointers whose positions
|
||||
// are included in the movement.
|
||||
// The positions array contains position information for each pointer in order by
|
||||
// increasing id. Its size should be equal to the number of one bits in idBits.
|
||||
void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
|
||||
|
||||
// Adds movement information for all pointers in a MotionEvent, including historical samples.
|
||||
void addMovement(const MotionEvent* event);
|
||||
|
||||
// Gets the velocity of the specified pointer id in position units per second.
|
||||
// Returns false and sets the velocity components to zero if there is
|
||||
// insufficient movement information for the pointer.
|
||||
bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
|
||||
|
||||
// Gets an estimator for the recent movements of the specified pointer id.
|
||||
// Returns false and clears the estimator if there is no information available
|
||||
// about the pointer.
|
||||
bool getEstimator(uint32_t id, Estimator* outEstimator) const;
|
||||
|
||||
// Gets the active pointer id, or -1 if none.
|
||||
inline int32_t getActivePointerId() const { return mActivePointerId; }
|
||||
|
||||
// Gets a bitset containing all pointer ids from the most recent movement.
|
||||
inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; }
|
||||
|
||||
private:
|
||||
static const char* DEFAULT_STRATEGY;
|
||||
|
||||
nsecs_t mLastEventTime;
|
||||
BitSet32 mCurrentPointerIdBits;
|
||||
int32_t mActivePointerId;
|
||||
VelocityTrackerStrategy* mStrategy;
|
||||
|
||||
bool configureStrategy(const char* strategy);
|
||||
|
||||
static VelocityTrackerStrategy* createStrategy(const char* strategy);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Implements a particular velocity tracker algorithm.
|
||||
*/
|
||||
class VelocityTrackerStrategy {
|
||||
protected:
|
||||
VelocityTrackerStrategy() { }
|
||||
|
||||
public:
|
||||
virtual ~VelocityTrackerStrategy() { }
|
||||
|
||||
virtual void clear() = 0;
|
||||
virtual void clearPointers(BitSet32 idBits) = 0;
|
||||
virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions) = 0;
|
||||
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Velocity tracker algorithm based on least-squares linear regression.
|
||||
*/
|
||||
class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
|
||||
public:
|
||||
enum Weighting {
|
||||
// No weights applied. All data points are equally reliable.
|
||||
WEIGHTING_NONE,
|
||||
|
||||
// Weight by time delta. Data points clustered together are weighted less.
|
||||
WEIGHTING_DELTA,
|
||||
|
||||
// Weight such that points within a certain horizon are weighed more than those
|
||||
// outside of that horizon.
|
||||
WEIGHTING_CENTRAL,
|
||||
|
||||
// Weight such that points older than a certain amount are weighed less.
|
||||
WEIGHTING_RECENT,
|
||||
};
|
||||
|
||||
// Degree must be no greater than Estimator::MAX_DEGREE.
|
||||
LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE);
|
||||
virtual ~LeastSquaresVelocityTrackerStrategy();
|
||||
|
||||
virtual void clear();
|
||||
virtual void clearPointers(BitSet32 idBits);
|
||||
virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions);
|
||||
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
|
||||
|
||||
private:
|
||||
// Sample horizon.
|
||||
// We don't use too much history by default since we want to react to quick
|
||||
// changes in direction.
|
||||
static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
|
||||
|
||||
// Number of samples to keep.
|
||||
static const uint32_t HISTORY_SIZE = 20;
|
||||
|
||||
struct Movement {
|
||||
nsecs_t eventTime;
|
||||
BitSet32 idBits;
|
||||
VelocityTracker::Position positions[MAX_POINTERS];
|
||||
|
||||
inline const VelocityTracker::Position& getPosition(uint32_t id) const {
|
||||
return positions[idBits.getIndexOfBit(id)];
|
||||
}
|
||||
};
|
||||
|
||||
float chooseWeight(uint32_t index) const;
|
||||
|
||||
const uint32_t mDegree;
|
||||
const Weighting mWeighting;
|
||||
uint32_t mIndex;
|
||||
Movement mMovements[HISTORY_SIZE];
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Velocity tracker algorithm that uses an IIR filter.
|
||||
*/
|
||||
class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
|
||||
public:
|
||||
// Degree must be 1 or 2.
|
||||
IntegratingVelocityTrackerStrategy(uint32_t degree);
|
||||
~IntegratingVelocityTrackerStrategy();
|
||||
|
||||
virtual void clear();
|
||||
virtual void clearPointers(BitSet32 idBits);
|
||||
virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions);
|
||||
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
|
||||
|
||||
private:
|
||||
// Current state estimate for a particular pointer.
|
||||
struct State {
|
||||
nsecs_t updateTime;
|
||||
uint32_t degree;
|
||||
|
||||
float xpos, xvel, xaccel;
|
||||
float ypos, yvel, yaccel;
|
||||
};
|
||||
|
||||
const uint32_t mDegree;
|
||||
BitSet32 mPointerIdBits;
|
||||
State mPointerState[MAX_POINTER_ID + 1];
|
||||
|
||||
void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
|
||||
void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
|
||||
void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Velocity tracker strategy used prior to ICS.
|
||||
*/
|
||||
class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
|
||||
public:
|
||||
LegacyVelocityTrackerStrategy();
|
||||
virtual ~LegacyVelocityTrackerStrategy();
|
||||
|
||||
virtual void clear();
|
||||
virtual void clearPointers(BitSet32 idBits);
|
||||
virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions);
|
||||
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
|
||||
|
||||
private:
|
||||
// Oldest sample to consider when calculating the velocity.
|
||||
static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
|
||||
|
||||
// Number of samples to keep.
|
||||
static const uint32_t HISTORY_SIZE = 20;
|
||||
|
||||
// The minimum duration between samples when estimating velocity.
|
||||
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
|
||||
|
||||
struct Movement {
|
||||
nsecs_t eventTime;
|
||||
BitSet32 idBits;
|
||||
VelocityTracker::Position positions[MAX_POINTERS];
|
||||
|
||||
inline const VelocityTracker::Position& getPosition(uint32_t id) const {
|
||||
return positions[idBits.getIndexOfBit(id)];
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t mIndex;
|
||||
Movement mMovements[HISTORY_SIZE];
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_VELOCITY_TRACKER_H
|
81
include/input/VirtualKeyMap.h
Normal file
81
include/input/VirtualKeyMap.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#ifndef _LIBINPUT_VIRTUAL_KEY_MAP_H
|
||||
#define _LIBINPUT_VIRTUAL_KEY_MAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <input/Input.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
#include <utils/Tokenizer.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Unicode.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
/* Describes a virtual key. */
|
||||
struct VirtualKeyDefinition {
|
||||
int32_t scanCode;
|
||||
|
||||
// configured position data, specified in display coords
|
||||
int32_t centerX;
|
||||
int32_t centerY;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Describes a collection of virtual keys on a touch screen in terms of
|
||||
* virtual scan codes and hit rectangles.
|
||||
*
|
||||
* This object is immutable after it has been loaded.
|
||||
*/
|
||||
class VirtualKeyMap {
|
||||
public:
|
||||
~VirtualKeyMap();
|
||||
|
||||
static status_t load(const String8& filename, VirtualKeyMap** outMap);
|
||||
|
||||
inline const Vector<VirtualKeyDefinition>& getVirtualKeys() const {
|
||||
return mVirtualKeys;
|
||||
}
|
||||
|
||||
private:
|
||||
class Parser {
|
||||
VirtualKeyMap* mMap;
|
||||
Tokenizer* mTokenizer;
|
||||
|
||||
public:
|
||||
Parser(VirtualKeyMap* map, Tokenizer* tokenizer);
|
||||
~Parser();
|
||||
status_t parse();
|
||||
|
||||
private:
|
||||
bool consumeFieldDelimiterAndSkipWhitespace();
|
||||
bool parseNextIntField(int32_t* outValue);
|
||||
};
|
||||
|
||||
Vector<VirtualKeyDefinition> mVirtualKeys;
|
||||
|
||||
VirtualKeyMap();
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBINPUT_KEY_CHARACTER_MAP_H
|
85
libs/input/Android.mk
Normal file
85
libs/input/Android.mk
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (C) 2013 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
# libinput is partially built for the host (used by build time keymap validation tool)
|
||||
# These files are common to host and target builds.
|
||||
|
||||
commonSources := \
|
||||
Input.cpp \
|
||||
InputDevice.cpp \
|
||||
Keyboard.cpp \
|
||||
KeyCharacterMap.cpp \
|
||||
KeyLayoutMap.cpp \
|
||||
VirtualKeyMap.cpp
|
||||
|
||||
deviceSources := \
|
||||
$(commonSources) \
|
||||
InputTransport.cpp \
|
||||
VelocityControl.cpp \
|
||||
VelocityTracker.cpp
|
||||
|
||||
hostSources := \
|
||||
$(commonSources)
|
||||
|
||||
# For the host
|
||||
# =====================================================
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= $(hostSources)
|
||||
|
||||
LOCAL_MODULE:= libinput
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_HOST_STATIC_LIBRARY)
|
||||
|
||||
|
||||
# For the device
|
||||
# =====================================================
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= $(deviceSources)
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
liblog \
|
||||
libcutils \
|
||||
libutils \
|
||||
libbinder \
|
||||
libskia \
|
||||
libz
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
external/skia/include/core \
|
||||
external/icu4c/common \
|
||||
external/zlib
|
||||
|
||||
LOCAL_MODULE:= libinput
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
|
||||
# Include subdirectory makefiles
|
||||
# ============================================================
|
||||
|
||||
# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
|
||||
# team really wants is to build the stuff defined by this makefile.
|
||||
ifeq (,$(ONE_SHOT_MAKEFILE))
|
||||
include $(call first-makefiles-under,$(LOCAL_PATH))
|
||||
endif
|
634
libs/input/Input.cpp
Normal file
634
libs/input/Input.cpp
Normal file
@ -0,0 +1,634 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "Input"
|
||||
//#define LOG_NDEBUG 0
|
||||
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <input/Input.h>
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
#include <binder/Parcel.h>
|
||||
|
||||
#include "SkPoint.h"
|
||||
#include "SkMatrix.h"
|
||||
#include "SkScalar.h"
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
|
||||
// --- InputEvent ---
|
||||
|
||||
void InputEvent::initialize(int32_t deviceId, int32_t source) {
|
||||
mDeviceId = deviceId;
|
||||
mSource = source;
|
||||
}
|
||||
|
||||
void InputEvent::initialize(const InputEvent& from) {
|
||||
mDeviceId = from.mDeviceId;
|
||||
mSource = from.mSource;
|
||||
}
|
||||
|
||||
// --- KeyEvent ---
|
||||
|
||||
bool KeyEvent::hasDefaultAction(int32_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case AKEYCODE_HOME:
|
||||
case AKEYCODE_BACK:
|
||||
case AKEYCODE_CALL:
|
||||
case AKEYCODE_ENDCALL:
|
||||
case AKEYCODE_VOLUME_UP:
|
||||
case AKEYCODE_VOLUME_DOWN:
|
||||
case AKEYCODE_VOLUME_MUTE:
|
||||
case AKEYCODE_POWER:
|
||||
case AKEYCODE_CAMERA:
|
||||
case AKEYCODE_HEADSETHOOK:
|
||||
case AKEYCODE_MENU:
|
||||
case AKEYCODE_NOTIFICATION:
|
||||
case AKEYCODE_FOCUS:
|
||||
case AKEYCODE_SEARCH:
|
||||
case AKEYCODE_MEDIA_PLAY:
|
||||
case AKEYCODE_MEDIA_PAUSE:
|
||||
case AKEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case AKEYCODE_MEDIA_STOP:
|
||||
case AKEYCODE_MEDIA_NEXT:
|
||||
case AKEYCODE_MEDIA_PREVIOUS:
|
||||
case AKEYCODE_MEDIA_REWIND:
|
||||
case AKEYCODE_MEDIA_RECORD:
|
||||
case AKEYCODE_MEDIA_FAST_FORWARD:
|
||||
case AKEYCODE_MUTE:
|
||||
case AKEYCODE_BRIGHTNESS_DOWN:
|
||||
case AKEYCODE_BRIGHTNESS_UP:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyEvent::hasDefaultAction() const {
|
||||
return hasDefaultAction(getKeyCode());
|
||||
}
|
||||
|
||||
bool KeyEvent::isSystemKey(int32_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case AKEYCODE_MENU:
|
||||
case AKEYCODE_SOFT_RIGHT:
|
||||
case AKEYCODE_HOME:
|
||||
case AKEYCODE_BACK:
|
||||
case AKEYCODE_CALL:
|
||||
case AKEYCODE_ENDCALL:
|
||||
case AKEYCODE_VOLUME_UP:
|
||||
case AKEYCODE_VOLUME_DOWN:
|
||||
case AKEYCODE_VOLUME_MUTE:
|
||||
case AKEYCODE_MUTE:
|
||||
case AKEYCODE_POWER:
|
||||
case AKEYCODE_HEADSETHOOK:
|
||||
case AKEYCODE_MEDIA_PLAY:
|
||||
case AKEYCODE_MEDIA_PAUSE:
|
||||
case AKEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case AKEYCODE_MEDIA_STOP:
|
||||
case AKEYCODE_MEDIA_NEXT:
|
||||
case AKEYCODE_MEDIA_PREVIOUS:
|
||||
case AKEYCODE_MEDIA_REWIND:
|
||||
case AKEYCODE_MEDIA_RECORD:
|
||||
case AKEYCODE_MEDIA_FAST_FORWARD:
|
||||
case AKEYCODE_CAMERA:
|
||||
case AKEYCODE_FOCUS:
|
||||
case AKEYCODE_SEARCH:
|
||||
case AKEYCODE_BRIGHTNESS_DOWN:
|
||||
case AKEYCODE_BRIGHTNESS_UP:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyEvent::isSystemKey() const {
|
||||
return isSystemKey(getKeyCode());
|
||||
}
|
||||
|
||||
void KeyEvent::initialize(
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t keyCode,
|
||||
int32_t scanCode,
|
||||
int32_t metaState,
|
||||
int32_t repeatCount,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime) {
|
||||
InputEvent::initialize(deviceId, source);
|
||||
mAction = action;
|
||||
mFlags = flags;
|
||||
mKeyCode = keyCode;
|
||||
mScanCode = scanCode;
|
||||
mMetaState = metaState;
|
||||
mRepeatCount = repeatCount;
|
||||
mDownTime = downTime;
|
||||
mEventTime = eventTime;
|
||||
}
|
||||
|
||||
void KeyEvent::initialize(const KeyEvent& from) {
|
||||
InputEvent::initialize(from);
|
||||
mAction = from.mAction;
|
||||
mFlags = from.mFlags;
|
||||
mKeyCode = from.mKeyCode;
|
||||
mScanCode = from.mScanCode;
|
||||
mMetaState = from.mMetaState;
|
||||
mRepeatCount = from.mRepeatCount;
|
||||
mDownTime = from.mDownTime;
|
||||
mEventTime = from.mEventTime;
|
||||
}
|
||||
|
||||
|
||||
// --- PointerCoords ---
|
||||
|
||||
float PointerCoords::getAxisValue(int32_t axis) const {
|
||||
if (axis < 0 || axis > 63) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t axisBit = 1LL << axis;
|
||||
if (!(bits & axisBit)) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
|
||||
return values[index];
|
||||
}
|
||||
|
||||
status_t PointerCoords::setAxisValue(int32_t axis, float value) {
|
||||
if (axis < 0 || axis > 63) {
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
uint64_t axisBit = 1LL << axis;
|
||||
uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
|
||||
if (!(bits & axisBit)) {
|
||||
if (value == 0) {
|
||||
return OK; // axes with value 0 do not need to be stored
|
||||
}
|
||||
uint32_t count = __builtin_popcountll(bits);
|
||||
if (count >= MAX_AXES) {
|
||||
tooManyAxes(axis);
|
||||
return NO_MEMORY;
|
||||
}
|
||||
bits |= axisBit;
|
||||
for (uint32_t i = count; i > index; i--) {
|
||||
values[i] = values[i - 1];
|
||||
}
|
||||
}
|
||||
values[index] = value;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) {
|
||||
float value = c.getAxisValue(axis);
|
||||
if (value != 0) {
|
||||
c.setAxisValue(axis, value * scaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
void PointerCoords::scale(float scaleFactor) {
|
||||
// No need to scale pressure or size since they are normalized.
|
||||
// No need to scale orientation since it is meaningless to do so.
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_X, scaleFactor);
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_Y, scaleFactor);
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor);
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor);
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor);
|
||||
scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
|
||||
}
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
status_t PointerCoords::readFromParcel(Parcel* parcel) {
|
||||
bits = parcel->readInt64();
|
||||
|
||||
uint32_t count = __builtin_popcountll(bits);
|
||||
if (count > MAX_AXES) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
values[i] = parcel->readFloat();
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t PointerCoords::writeToParcel(Parcel* parcel) const {
|
||||
parcel->writeInt64(bits);
|
||||
|
||||
uint32_t count = __builtin_popcountll(bits);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
parcel->writeFloat(values[i]);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
void PointerCoords::tooManyAxes(int axis) {
|
||||
ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
|
||||
"cannot contain more than %d axis values.", axis, int(MAX_AXES));
|
||||
}
|
||||
|
||||
bool PointerCoords::operator==(const PointerCoords& other) const {
|
||||
if (bits != other.bits) {
|
||||
return false;
|
||||
}
|
||||
uint32_t count = __builtin_popcountll(bits);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (values[i] != other.values[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PointerCoords::copyFrom(const PointerCoords& other) {
|
||||
bits = other.bits;
|
||||
uint32_t count = __builtin_popcountll(bits);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
values[i] = other.values[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- PointerProperties ---
|
||||
|
||||
bool PointerProperties::operator==(const PointerProperties& other) const {
|
||||
return id == other.id
|
||||
&& toolType == other.toolType;
|
||||
}
|
||||
|
||||
void PointerProperties::copyFrom(const PointerProperties& other) {
|
||||
id = other.id;
|
||||
toolType = other.toolType;
|
||||
}
|
||||
|
||||
|
||||
// --- MotionEvent ---
|
||||
|
||||
void MotionEvent::initialize(
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t edgeFlags,
|
||||
int32_t metaState,
|
||||
int32_t buttonState,
|
||||
float xOffset,
|
||||
float yOffset,
|
||||
float xPrecision,
|
||||
float yPrecision,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime,
|
||||
size_t pointerCount,
|
||||
const PointerProperties* pointerProperties,
|
||||
const PointerCoords* pointerCoords) {
|
||||
InputEvent::initialize(deviceId, source);
|
||||
mAction = action;
|
||||
mFlags = flags;
|
||||
mEdgeFlags = edgeFlags;
|
||||
mMetaState = metaState;
|
||||
mButtonState = buttonState;
|
||||
mXOffset = xOffset;
|
||||
mYOffset = yOffset;
|
||||
mXPrecision = xPrecision;
|
||||
mYPrecision = yPrecision;
|
||||
mDownTime = downTime;
|
||||
mPointerProperties.clear();
|
||||
mPointerProperties.appendArray(pointerProperties, pointerCount);
|
||||
mSampleEventTimes.clear();
|
||||
mSamplePointerCoords.clear();
|
||||
addSample(eventTime, pointerCoords);
|
||||
}
|
||||
|
||||
void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) {
|
||||
InputEvent::initialize(other->mDeviceId, other->mSource);
|
||||
mAction = other->mAction;
|
||||
mFlags = other->mFlags;
|
||||
mEdgeFlags = other->mEdgeFlags;
|
||||
mMetaState = other->mMetaState;
|
||||
mButtonState = other->mButtonState;
|
||||
mXOffset = other->mXOffset;
|
||||
mYOffset = other->mYOffset;
|
||||
mXPrecision = other->mXPrecision;
|
||||
mYPrecision = other->mYPrecision;
|
||||
mDownTime = other->mDownTime;
|
||||
mPointerProperties = other->mPointerProperties;
|
||||
|
||||
if (keepHistory) {
|
||||
mSampleEventTimes = other->mSampleEventTimes;
|
||||
mSamplePointerCoords = other->mSamplePointerCoords;
|
||||
} else {
|
||||
mSampleEventTimes.clear();
|
||||
mSampleEventTimes.push(other->getEventTime());
|
||||
mSamplePointerCoords.clear();
|
||||
size_t pointerCount = other->getPointerCount();
|
||||
size_t historySize = other->getHistorySize();
|
||||
mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array()
|
||||
+ (historySize * pointerCount), pointerCount);
|
||||
}
|
||||
}
|
||||
|
||||
void MotionEvent::addSample(
|
||||
int64_t eventTime,
|
||||
const PointerCoords* pointerCoords) {
|
||||
mSampleEventTimes.push(eventTime);
|
||||
mSamplePointerCoords.appendArray(pointerCoords, getPointerCount());
|
||||
}
|
||||
|
||||
const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const {
|
||||
return &mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex];
|
||||
}
|
||||
|
||||
float MotionEvent::getRawAxisValue(int32_t axis, size_t pointerIndex) const {
|
||||
return getRawPointerCoords(pointerIndex)->getAxisValue(axis);
|
||||
}
|
||||
|
||||
float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const {
|
||||
float value = getRawPointerCoords(pointerIndex)->getAxisValue(axis);
|
||||
switch (axis) {
|
||||
case AMOTION_EVENT_AXIS_X:
|
||||
return value + mXOffset;
|
||||
case AMOTION_EVENT_AXIS_Y:
|
||||
return value + mYOffset;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
|
||||
size_t pointerIndex, size_t historicalIndex) const {
|
||||
return &mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex];
|
||||
}
|
||||
|
||||
float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
|
||||
size_t historicalIndex) const {
|
||||
return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
|
||||
}
|
||||
|
||||
float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
|
||||
size_t historicalIndex) const {
|
||||
float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
|
||||
switch (axis) {
|
||||
case AMOTION_EVENT_AXIS_X:
|
||||
return value + mXOffset;
|
||||
case AMOTION_EVENT_AXIS_Y:
|
||||
return value + mYOffset;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
|
||||
size_t pointerCount = mPointerProperties.size();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
if (mPointerProperties.itemAt(i).id == pointerId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MotionEvent::offsetLocation(float xOffset, float yOffset) {
|
||||
mXOffset += xOffset;
|
||||
mYOffset += yOffset;
|
||||
}
|
||||
|
||||
void MotionEvent::scale(float scaleFactor) {
|
||||
mXOffset *= scaleFactor;
|
||||
mYOffset *= scaleFactor;
|
||||
mXPrecision *= scaleFactor;
|
||||
mYPrecision *= scaleFactor;
|
||||
|
||||
size_t numSamples = mSamplePointerCoords.size();
|
||||
for (size_t i = 0; i < numSamples; i++) {
|
||||
mSamplePointerCoords.editItemAt(i).scale(scaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ANDROID_OS
|
||||
static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
|
||||
// Construct and transform a vector oriented at the specified clockwise angle from vertical.
|
||||
// Coordinate system: down is increasing Y, right is increasing X.
|
||||
SkPoint vector;
|
||||
vector.fX = SkFloatToScalar(sinf(angleRadians));
|
||||
vector.fY = SkFloatToScalar(-cosf(angleRadians));
|
||||
matrix->mapVectors(& vector, 1);
|
||||
|
||||
// Derive the transformed vector's clockwise angle from vertical.
|
||||
float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(-vector.fY));
|
||||
if (result < - M_PI_2) {
|
||||
result += M_PI;
|
||||
} else if (result > M_PI_2) {
|
||||
result -= M_PI;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void MotionEvent::transform(const SkMatrix* matrix) {
|
||||
float oldXOffset = mXOffset;
|
||||
float oldYOffset = mYOffset;
|
||||
|
||||
// The tricky part of this implementation is to preserve the value of
|
||||
// rawX and rawY. So we apply the transformation to the first point
|
||||
// then derive an appropriate new X/Y offset that will preserve rawX and rawY.
|
||||
SkPoint point;
|
||||
float rawX = getRawX(0);
|
||||
float rawY = getRawY(0);
|
||||
matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
|
||||
& point);
|
||||
float newX = SkScalarToFloat(point.fX);
|
||||
float newY = SkScalarToFloat(point.fY);
|
||||
float newXOffset = newX - rawX;
|
||||
float newYOffset = newY - rawY;
|
||||
|
||||
mXOffset = newXOffset;
|
||||
mYOffset = newYOffset;
|
||||
|
||||
// Apply the transformation to all samples.
|
||||
size_t numSamples = mSamplePointerCoords.size();
|
||||
for (size_t i = 0; i < numSamples; i++) {
|
||||
PointerCoords& c = mSamplePointerCoords.editItemAt(i);
|
||||
float x = c.getAxisValue(AMOTION_EVENT_AXIS_X) + oldXOffset;
|
||||
float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y) + oldYOffset;
|
||||
matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), &point);
|
||||
c.setAxisValue(AMOTION_EVENT_AXIS_X, SkScalarToFloat(point.fX) - newXOffset);
|
||||
c.setAxisValue(AMOTION_EVENT_AXIS_Y, SkScalarToFloat(point.fY) - newYOffset);
|
||||
|
||||
float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(matrix, orientation));
|
||||
}
|
||||
}
|
||||
|
||||
status_t MotionEvent::readFromParcel(Parcel* parcel) {
|
||||
size_t pointerCount = parcel->readInt32();
|
||||
size_t sampleCount = parcel->readInt32();
|
||||
if (pointerCount == 0 || pointerCount > MAX_POINTERS || sampleCount == 0) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
mDeviceId = parcel->readInt32();
|
||||
mSource = parcel->readInt32();
|
||||
mAction = parcel->readInt32();
|
||||
mFlags = parcel->readInt32();
|
||||
mEdgeFlags = parcel->readInt32();
|
||||
mMetaState = parcel->readInt32();
|
||||
mButtonState = parcel->readInt32();
|
||||
mXOffset = parcel->readFloat();
|
||||
mYOffset = parcel->readFloat();
|
||||
mXPrecision = parcel->readFloat();
|
||||
mYPrecision = parcel->readFloat();
|
||||
mDownTime = parcel->readInt64();
|
||||
|
||||
mPointerProperties.clear();
|
||||
mPointerProperties.setCapacity(pointerCount);
|
||||
mSampleEventTimes.clear();
|
||||
mSampleEventTimes.setCapacity(sampleCount);
|
||||
mSamplePointerCoords.clear();
|
||||
mSamplePointerCoords.setCapacity(sampleCount * pointerCount);
|
||||
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
mPointerProperties.push();
|
||||
PointerProperties& properties = mPointerProperties.editTop();
|
||||
properties.id = parcel->readInt32();
|
||||
properties.toolType = parcel->readInt32();
|
||||
}
|
||||
|
||||
while (sampleCount-- > 0) {
|
||||
mSampleEventTimes.push(parcel->readInt64());
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
mSamplePointerCoords.push();
|
||||
status_t status = mSamplePointerCoords.editTop().readFromParcel(parcel);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t MotionEvent::writeToParcel(Parcel* parcel) const {
|
||||
size_t pointerCount = mPointerProperties.size();
|
||||
size_t sampleCount = mSampleEventTimes.size();
|
||||
|
||||
parcel->writeInt32(pointerCount);
|
||||
parcel->writeInt32(sampleCount);
|
||||
|
||||
parcel->writeInt32(mDeviceId);
|
||||
parcel->writeInt32(mSource);
|
||||
parcel->writeInt32(mAction);
|
||||
parcel->writeInt32(mFlags);
|
||||
parcel->writeInt32(mEdgeFlags);
|
||||
parcel->writeInt32(mMetaState);
|
||||
parcel->writeInt32(mButtonState);
|
||||
parcel->writeFloat(mXOffset);
|
||||
parcel->writeFloat(mYOffset);
|
||||
parcel->writeFloat(mXPrecision);
|
||||
parcel->writeFloat(mYPrecision);
|
||||
parcel->writeInt64(mDownTime);
|
||||
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
const PointerProperties& properties = mPointerProperties.itemAt(i);
|
||||
parcel->writeInt32(properties.id);
|
||||
parcel->writeInt32(properties.toolType);
|
||||
}
|
||||
|
||||
const PointerCoords* pc = mSamplePointerCoords.array();
|
||||
for (size_t h = 0; h < sampleCount; h++) {
|
||||
parcel->writeInt64(mSampleEventTimes.itemAt(h));
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
status_t status = (pc++)->writeToParcel(parcel);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
|
||||
if (source & AINPUT_SOURCE_CLASS_POINTER) {
|
||||
// Specifically excludes HOVER_MOVE and SCROLL.
|
||||
switch (action & AMOTION_EVENT_ACTION_MASK) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// --- PooledInputEventFactory ---
|
||||
|
||||
PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) :
|
||||
mMaxPoolSize(maxPoolSize) {
|
||||
}
|
||||
|
||||
PooledInputEventFactory::~PooledInputEventFactory() {
|
||||
for (size_t i = 0; i < mKeyEventPool.size(); i++) {
|
||||
delete mKeyEventPool.itemAt(i);
|
||||
}
|
||||
for (size_t i = 0; i < mMotionEventPool.size(); i++) {
|
||||
delete mMotionEventPool.itemAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
KeyEvent* PooledInputEventFactory::createKeyEvent() {
|
||||
if (!mKeyEventPool.isEmpty()) {
|
||||
KeyEvent* event = mKeyEventPool.top();
|
||||
mKeyEventPool.pop();
|
||||
return event;
|
||||
}
|
||||
return new KeyEvent();
|
||||
}
|
||||
|
||||
MotionEvent* PooledInputEventFactory::createMotionEvent() {
|
||||
if (!mMotionEventPool.isEmpty()) {
|
||||
MotionEvent* event = mMotionEventPool.top();
|
||||
mMotionEventPool.pop();
|
||||
return event;
|
||||
}
|
||||
return new MotionEvent();
|
||||
}
|
||||
|
||||
void PooledInputEventFactory::recycle(InputEvent* event) {
|
||||
switch (event->getType()) {
|
||||
case AINPUT_EVENT_TYPE_KEY:
|
||||
if (mKeyEventPool.size() < mMaxPoolSize) {
|
||||
mKeyEventPool.push(static_cast<KeyEvent*>(event));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case AINPUT_EVENT_TYPE_MOTION:
|
||||
if (mMotionEventPool.size() < mMaxPoolSize) {
|
||||
mMotionEventPool.push(static_cast<MotionEvent*>(event));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
delete event;
|
||||
}
|
||||
|
||||
} // namespace android
|
184
libs/input/InputDevice.cpp
Normal file
184
libs/input/InputDevice.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "InputDevice"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <input/InputDevice.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
static const char* CONFIGURATION_FILE_DIR[] = {
|
||||
"idc/",
|
||||
"keylayout/",
|
||||
"keychars/",
|
||||
};
|
||||
|
||||
static const char* CONFIGURATION_FILE_EXTENSION[] = {
|
||||
".idc",
|
||||
".kl",
|
||||
".kcm",
|
||||
};
|
||||
|
||||
static bool isValidNameChar(char ch) {
|
||||
return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_');
|
||||
}
|
||||
|
||||
static void appendInputDeviceConfigurationFileRelativePath(String8& path,
|
||||
const String8& name, InputDeviceConfigurationFileType type) {
|
||||
path.append(CONFIGURATION_FILE_DIR[type]);
|
||||
for (size_t i = 0; i < name.length(); i++) {
|
||||
char ch = name[i];
|
||||
if (!isValidNameChar(ch)) {
|
||||
ch = '_';
|
||||
}
|
||||
path.append(&ch, 1);
|
||||
}
|
||||
path.append(CONFIGURATION_FILE_EXTENSION[type]);
|
||||
}
|
||||
|
||||
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
|
||||
const InputDeviceIdentifier& deviceIdentifier,
|
||||
InputDeviceConfigurationFileType type) {
|
||||
if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
|
||||
if (deviceIdentifier.version != 0) {
|
||||
// Try vendor product version.
|
||||
String8 versionPath(getInputDeviceConfigurationFilePathByName(
|
||||
String8::format("Vendor_%04x_Product_%04x_Version_%04x",
|
||||
deviceIdentifier.vendor, deviceIdentifier.product,
|
||||
deviceIdentifier.version),
|
||||
type));
|
||||
if (!versionPath.isEmpty()) {
|
||||
return versionPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try vendor product.
|
||||
String8 productPath(getInputDeviceConfigurationFilePathByName(
|
||||
String8::format("Vendor_%04x_Product_%04x",
|
||||
deviceIdentifier.vendor, deviceIdentifier.product),
|
||||
type));
|
||||
if (!productPath.isEmpty()) {
|
||||
return productPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try device name.
|
||||
return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
|
||||
}
|
||||
|
||||
String8 getInputDeviceConfigurationFilePathByName(
|
||||
const String8& name, InputDeviceConfigurationFileType type) {
|
||||
// Search system repository.
|
||||
String8 path;
|
||||
path.setTo(getenv("ANDROID_ROOT"));
|
||||
path.append("/usr/");
|
||||
appendInputDeviceConfigurationFileRelativePath(path, name, type);
|
||||
#if DEBUG_PROBE
|
||||
ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
|
||||
#endif
|
||||
if (!access(path.string(), R_OK)) {
|
||||
#if DEBUG_PROBE
|
||||
ALOGD("Found");
|
||||
#endif
|
||||
return path;
|
||||
}
|
||||
|
||||
// Search user repository.
|
||||
// TODO Should only look here if not in safe mode.
|
||||
path.setTo(getenv("ANDROID_DATA"));
|
||||
path.append("/system/devices/");
|
||||
appendInputDeviceConfigurationFileRelativePath(path, name, type);
|
||||
#if DEBUG_PROBE
|
||||
ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
|
||||
#endif
|
||||
if (!access(path.string(), R_OK)) {
|
||||
#if DEBUG_PROBE
|
||||
ALOGD("Found");
|
||||
#endif
|
||||
return path;
|
||||
}
|
||||
|
||||
// Not found.
|
||||
#if DEBUG_PROBE
|
||||
ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
|
||||
name.string(), type);
|
||||
#endif
|
||||
return String8();
|
||||
}
|
||||
|
||||
|
||||
// --- InputDeviceInfo ---
|
||||
|
||||
InputDeviceInfo::InputDeviceInfo() {
|
||||
initialize(-1, -1, InputDeviceIdentifier(), String8(), false);
|
||||
}
|
||||
|
||||
InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
|
||||
mId(other.mId), mGeneration(other.mGeneration), mIdentifier(other.mIdentifier),
|
||||
mAlias(other.mAlias), mIsExternal(other.mIsExternal), mSources(other.mSources),
|
||||
mKeyboardType(other.mKeyboardType),
|
||||
mKeyCharacterMap(other.mKeyCharacterMap),
|
||||
mHasVibrator(other.mHasVibrator),
|
||||
mMotionRanges(other.mMotionRanges) {
|
||||
}
|
||||
|
||||
InputDeviceInfo::~InputDeviceInfo() {
|
||||
}
|
||||
|
||||
void InputDeviceInfo::initialize(int32_t id, int32_t generation,
|
||||
const InputDeviceIdentifier& identifier, const String8& alias, bool isExternal) {
|
||||
mId = id;
|
||||
mGeneration = generation;
|
||||
mIdentifier = identifier;
|
||||
mAlias = alias;
|
||||
mIsExternal = isExternal;
|
||||
mSources = 0;
|
||||
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
|
||||
mHasVibrator = false;
|
||||
mMotionRanges.clear();
|
||||
}
|
||||
|
||||
const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
|
||||
int32_t axis, uint32_t source) const {
|
||||
size_t numRanges = mMotionRanges.size();
|
||||
for (size_t i = 0; i < numRanges; i++) {
|
||||
const MotionRange& range = mMotionRanges.itemAt(i);
|
||||
if (range.axis == axis && range.source == source) {
|
||||
return ⦥
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void InputDeviceInfo::addSource(uint32_t source) {
|
||||
mSources |= source;
|
||||
}
|
||||
|
||||
void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
|
||||
float flat, float fuzz, float resolution) {
|
||||
MotionRange range = { axis, source, min, max, flat, fuzz, resolution };
|
||||
mMotionRanges.add(range);
|
||||
}
|
||||
|
||||
void InputDeviceInfo::addMotionRange(const MotionRange& range) {
|
||||
mMotionRanges.add(range);
|
||||
}
|
||||
|
||||
} // namespace android
|
958
libs/input/InputTransport.cpp
Normal file
958
libs/input/InputTransport.cpp
Normal file
@ -0,0 +1,958 @@
|
||||
//
|
||||
// Copyright 2010 The Android Open Source Project
|
||||
//
|
||||
// Provides a shared memory transport for input events.
|
||||
//
|
||||
#define LOG_TAG "InputTransport"
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
|
||||
// Log debug messages about channel messages (send message, receive message)
|
||||
#define DEBUG_CHANNEL_MESSAGES 0
|
||||
|
||||
// Log debug messages whenever InputChannel objects are created/destroyed
|
||||
#define DEBUG_CHANNEL_LIFECYCLE 0
|
||||
|
||||
// Log debug messages about transport actions
|
||||
#define DEBUG_TRANSPORT_ACTIONS 0
|
||||
|
||||
// Log debug messages about touch event resampling
|
||||
#define DEBUG_RESAMPLING 0
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cutils/log.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <input/InputTransport.h>
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
// Socket buffer size. The default is typically about 128KB, which is much larger than
|
||||
// we really need. So we make it smaller. It just needs to be big enough to hold
|
||||
// a few dozen large multi-finger motion events in the case where an application gets
|
||||
// behind processing touches.
|
||||
static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
|
||||
|
||||
// Nanoseconds per milliseconds.
|
||||
static const nsecs_t NANOS_PER_MS = 1000000;
|
||||
|
||||
// Latency added during resampling. A few milliseconds doesn't hurt much but
|
||||
// reduces the impact of mispredicted touch positions.
|
||||
static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS;
|
||||
|
||||
// Minimum time difference between consecutive samples before attempting to resample.
|
||||
static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
|
||||
|
||||
// Maximum time to predict forward from the last known state, to avoid predicting too
|
||||
// far into the future. This time is further bounded by 50% of the last time delta.
|
||||
static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
|
||||
|
||||
template<typename T>
|
||||
inline static T min(const T& a, const T& b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
inline static float lerp(float a, float b, float alpha) {
|
||||
return a + alpha * (b - a);
|
||||
}
|
||||
|
||||
// --- InputMessage ---
|
||||
|
||||
bool InputMessage::isValid(size_t actualSize) const {
|
||||
if (size() == actualSize) {
|
||||
switch (header.type) {
|
||||
case TYPE_KEY:
|
||||
return true;
|
||||
case TYPE_MOTION:
|
||||
return body.motion.pointerCount > 0
|
||||
&& body.motion.pointerCount <= MAX_POINTERS;
|
||||
case TYPE_FINISHED:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t InputMessage::size() const {
|
||||
switch (header.type) {
|
||||
case TYPE_KEY:
|
||||
return sizeof(Header) + body.key.size();
|
||||
case TYPE_MOTION:
|
||||
return sizeof(Header) + body.motion.size();
|
||||
case TYPE_FINISHED:
|
||||
return sizeof(Header) + body.finished.size();
|
||||
}
|
||||
return sizeof(Header);
|
||||
}
|
||||
|
||||
|
||||
// --- InputChannel ---
|
||||
|
||||
InputChannel::InputChannel(const String8& name, int fd) :
|
||||
mName(name), mFd(fd) {
|
||||
#if DEBUG_CHANNEL_LIFECYCLE
|
||||
ALOGD("Input channel constructed: name='%s', fd=%d",
|
||||
mName.string(), fd);
|
||||
#endif
|
||||
|
||||
int result = fcntl(mFd, F_SETFL, O_NONBLOCK);
|
||||
LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make socket "
|
||||
"non-blocking. errno=%d", mName.string(), errno);
|
||||
}
|
||||
|
||||
InputChannel::~InputChannel() {
|
||||
#if DEBUG_CHANNEL_LIFECYCLE
|
||||
ALOGD("Input channel destroyed: name='%s', fd=%d",
|
||||
mName.string(), mFd);
|
||||
#endif
|
||||
|
||||
::close(mFd);
|
||||
}
|
||||
|
||||
status_t InputChannel::openInputChannelPair(const String8& name,
|
||||
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
|
||||
int sockets[2];
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
|
||||
status_t result = -errno;
|
||||
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
|
||||
name.string(), errno);
|
||||
outServerChannel.clear();
|
||||
outClientChannel.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
int bufferSize = SOCKET_BUFFER_SIZE;
|
||||
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
|
||||
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
|
||||
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
|
||||
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
|
||||
|
||||
String8 serverChannelName = name;
|
||||
serverChannelName.append(" (server)");
|
||||
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
|
||||
|
||||
String8 clientChannelName = name;
|
||||
clientChannelName.append(" (client)");
|
||||
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputChannel::sendMessage(const InputMessage* msg) {
|
||||
size_t msgLength = msg->size();
|
||||
ssize_t nWrite;
|
||||
do {
|
||||
nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
} while (nWrite == -1 && errno == EINTR);
|
||||
|
||||
if (nWrite < 0) {
|
||||
int error = errno;
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ error sending message of type %d, errno=%d", mName.string(),
|
||||
msg->header.type, error);
|
||||
#endif
|
||||
if (error == EAGAIN || error == EWOULDBLOCK) {
|
||||
return WOULD_BLOCK;
|
||||
}
|
||||
if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED || error == ECONNRESET) {
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
return -error;
|
||||
}
|
||||
|
||||
if (size_t(nWrite) != msgLength) {
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
|
||||
mName.string(), msg->header.type);
|
||||
#endif
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ sent message of type %d", mName.string(), msg->header.type);
|
||||
#endif
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputChannel::receiveMessage(InputMessage* msg) {
|
||||
ssize_t nRead;
|
||||
do {
|
||||
nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
|
||||
} while (nRead == -1 && errno == EINTR);
|
||||
|
||||
if (nRead < 0) {
|
||||
int error = errno;
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.string(), errno);
|
||||
#endif
|
||||
if (error == EAGAIN || error == EWOULDBLOCK) {
|
||||
return WOULD_BLOCK;
|
||||
}
|
||||
if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED) {
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
return -error;
|
||||
}
|
||||
|
||||
if (nRead == 0) { // check for EOF
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.string());
|
||||
#endif
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
|
||||
if (!msg->isValid(nRead)) {
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ received invalid message", mName.string());
|
||||
#endif
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
#if DEBUG_CHANNEL_MESSAGES
|
||||
ALOGD("channel '%s' ~ received message of type %d", mName.string(), msg->header.type);
|
||||
#endif
|
||||
return OK;
|
||||
}
|
||||
|
||||
sp<InputChannel> InputChannel::dup() const {
|
||||
int fd = ::dup(getFd());
|
||||
return fd >= 0 ? new InputChannel(getName(), fd) : NULL;
|
||||
}
|
||||
|
||||
|
||||
// --- InputPublisher ---
|
||||
|
||||
InputPublisher::InputPublisher(const sp<InputChannel>& channel) :
|
||||
mChannel(channel) {
|
||||
}
|
||||
|
||||
InputPublisher::~InputPublisher() {
|
||||
}
|
||||
|
||||
status_t InputPublisher::publishKeyEvent(
|
||||
uint32_t seq,
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t keyCode,
|
||||
int32_t scanCode,
|
||||
int32_t metaState,
|
||||
int32_t repeatCount,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
|
||||
"action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
|
||||
"downTime=%lld, eventTime=%lld",
|
||||
mChannel->getName().string(), seq,
|
||||
deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
|
||||
downTime, eventTime);
|
||||
#endif
|
||||
|
||||
if (!seq) {
|
||||
ALOGE("Attempted to publish a key event with sequence number 0.");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
InputMessage msg;
|
||||
msg.header.type = InputMessage::TYPE_KEY;
|
||||
msg.body.key.seq = seq;
|
||||
msg.body.key.deviceId = deviceId;
|
||||
msg.body.key.source = source;
|
||||
msg.body.key.action = action;
|
||||
msg.body.key.flags = flags;
|
||||
msg.body.key.keyCode = keyCode;
|
||||
msg.body.key.scanCode = scanCode;
|
||||
msg.body.key.metaState = metaState;
|
||||
msg.body.key.repeatCount = repeatCount;
|
||||
msg.body.key.downTime = downTime;
|
||||
msg.body.key.eventTime = eventTime;
|
||||
return mChannel->sendMessage(&msg);
|
||||
}
|
||||
|
||||
status_t InputPublisher::publishMotionEvent(
|
||||
uint32_t seq,
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
int32_t action,
|
||||
int32_t flags,
|
||||
int32_t edgeFlags,
|
||||
int32_t metaState,
|
||||
int32_t buttonState,
|
||||
float xOffset,
|
||||
float yOffset,
|
||||
float xPrecision,
|
||||
float yPrecision,
|
||||
nsecs_t downTime,
|
||||
nsecs_t eventTime,
|
||||
size_t pointerCount,
|
||||
const PointerProperties* pointerProperties,
|
||||
const PointerCoords* pointerCoords) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
|
||||
"action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, buttonState=0x%x, "
|
||||
"xOffset=%f, yOffset=%f, "
|
||||
"xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
|
||||
"pointerCount=%d",
|
||||
mChannel->getName().string(), seq,
|
||||
deviceId, source, action, flags, edgeFlags, metaState, buttonState,
|
||||
xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount);
|
||||
#endif
|
||||
|
||||
if (!seq) {
|
||||
ALOGE("Attempted to publish a motion event with sequence number 0.");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
if (pointerCount > MAX_POINTERS || pointerCount < 1) {
|
||||
ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.",
|
||||
mChannel->getName().string(), pointerCount);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
InputMessage msg;
|
||||
msg.header.type = InputMessage::TYPE_MOTION;
|
||||
msg.body.motion.seq = seq;
|
||||
msg.body.motion.deviceId = deviceId;
|
||||
msg.body.motion.source = source;
|
||||
msg.body.motion.action = action;
|
||||
msg.body.motion.flags = flags;
|
||||
msg.body.motion.edgeFlags = edgeFlags;
|
||||
msg.body.motion.metaState = metaState;
|
||||
msg.body.motion.buttonState = buttonState;
|
||||
msg.body.motion.xOffset = xOffset;
|
||||
msg.body.motion.yOffset = yOffset;
|
||||
msg.body.motion.xPrecision = xPrecision;
|
||||
msg.body.motion.yPrecision = yPrecision;
|
||||
msg.body.motion.downTime = downTime;
|
||||
msg.body.motion.eventTime = eventTime;
|
||||
msg.body.motion.pointerCount = pointerCount;
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
|
||||
msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
|
||||
}
|
||||
return mChannel->sendMessage(&msg);
|
||||
}
|
||||
|
||||
status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' publisher ~ receiveFinishedSignal",
|
||||
mChannel->getName().string());
|
||||
#endif
|
||||
|
||||
InputMessage msg;
|
||||
status_t result = mChannel->receiveMessage(&msg);
|
||||
if (result) {
|
||||
*outSeq = 0;
|
||||
*outHandled = false;
|
||||
return result;
|
||||
}
|
||||
if (msg.header.type != InputMessage::TYPE_FINISHED) {
|
||||
ALOGE("channel '%s' publisher ~ Received unexpected message of type %d from consumer",
|
||||
mChannel->getName().string(), msg.header.type);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
*outSeq = msg.body.finished.seq;
|
||||
*outHandled = msg.body.finished.handled;
|
||||
return OK;
|
||||
}
|
||||
|
||||
// --- InputConsumer ---
|
||||
|
||||
InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
|
||||
mResampleTouch(isTouchResamplingEnabled()),
|
||||
mChannel(channel), mMsgDeferred(false) {
|
||||
}
|
||||
|
||||
InputConsumer::~InputConsumer() {
|
||||
}
|
||||
|
||||
bool InputConsumer::isTouchResamplingEnabled() {
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
int length = property_get("debug.inputconsumer.resample", value, NULL);
|
||||
if (length > 0) {
|
||||
if (!strcmp("0", value)) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp("1", value)) {
|
||||
ALOGD("Unrecognized property value for 'debug.inputconsumer.resample'. "
|
||||
"Use '1' or '0'.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
status_t InputConsumer::consume(InputEventFactoryInterface* factory,
|
||||
bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld",
|
||||
mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime);
|
||||
#endif
|
||||
|
||||
*outSeq = 0;
|
||||
*outEvent = NULL;
|
||||
|
||||
// Fetch the next input message.
|
||||
// Loop until an event can be returned or no additional events are received.
|
||||
while (!*outEvent) {
|
||||
if (mMsgDeferred) {
|
||||
// mMsg contains a valid input message from the previous call to consume
|
||||
// that has not yet been processed.
|
||||
mMsgDeferred = false;
|
||||
} else {
|
||||
// Receive a fresh message.
|
||||
status_t result = mChannel->receiveMessage(&mMsg);
|
||||
if (result) {
|
||||
// Consume the next batched event unless batches are being held for later.
|
||||
if (consumeBatches || result != WOULD_BLOCK) {
|
||||
result = consumeBatch(factory, frameTime, outSeq, outEvent);
|
||||
if (*outEvent) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
|
||||
mChannel->getName().string(), *outSeq);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mMsg.header.type) {
|
||||
case InputMessage::TYPE_KEY: {
|
||||
KeyEvent* keyEvent = factory->createKeyEvent();
|
||||
if (!keyEvent) return NO_MEMORY;
|
||||
|
||||
initializeKeyEvent(keyEvent, &mMsg);
|
||||
*outSeq = mMsg.body.key.seq;
|
||||
*outEvent = keyEvent;
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
|
||||
mChannel->getName().string(), *outSeq);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case AINPUT_EVENT_TYPE_MOTION: {
|
||||
ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
|
||||
if (batchIndex >= 0) {
|
||||
Batch& batch = mBatches.editItemAt(batchIndex);
|
||||
if (canAddSample(batch, &mMsg)) {
|
||||
batch.samples.push(mMsg);
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ appended to batch event",
|
||||
mChannel->getName().string());
|
||||
#endif
|
||||
break;
|
||||
} else {
|
||||
// We cannot append to the batch in progress, so we need to consume
|
||||
// the previous batch right now and defer the new message until later.
|
||||
mMsgDeferred = true;
|
||||
status_t result = consumeSamples(factory,
|
||||
batch, batch.samples.size(), outSeq, outEvent);
|
||||
mBatches.removeAt(batchIndex);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ consumed batch event and "
|
||||
"deferred current event, seq=%u",
|
||||
mChannel->getName().string(), *outSeq);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Start a new batch if needed.
|
||||
if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE
|
||||
|| mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
|
||||
mBatches.push();
|
||||
Batch& batch = mBatches.editTop();
|
||||
batch.samples.push(mMsg);
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ started batch event",
|
||||
mChannel->getName().string());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
MotionEvent* motionEvent = factory->createMotionEvent();
|
||||
if (! motionEvent) return NO_MEMORY;
|
||||
|
||||
updateTouchState(&mMsg);
|
||||
initializeMotionEvent(motionEvent, &mMsg);
|
||||
*outSeq = mMsg.body.motion.seq;
|
||||
*outEvent = motionEvent;
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
|
||||
mChannel->getName().string(), *outSeq);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ALOGE("channel '%s' consumer ~ Received unexpected message of type %d",
|
||||
mChannel->getName().string(), mMsg.header.type);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
|
||||
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
|
||||
status_t result;
|
||||
for (size_t i = mBatches.size(); i-- > 0; ) {
|
||||
Batch& batch = mBatches.editItemAt(i);
|
||||
if (frameTime < 0) {
|
||||
result = consumeSamples(factory, batch, batch.samples.size(),
|
||||
outSeq, outEvent);
|
||||
mBatches.removeAt(i);
|
||||
return result;
|
||||
}
|
||||
|
||||
nsecs_t sampleTime = frameTime - RESAMPLE_LATENCY;
|
||||
ssize_t split = findSampleNoLaterThan(batch, sampleTime);
|
||||
if (split < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
|
||||
const InputMessage* next;
|
||||
if (batch.samples.isEmpty()) {
|
||||
mBatches.removeAt(i);
|
||||
next = NULL;
|
||||
} else {
|
||||
next = &batch.samples.itemAt(0);
|
||||
}
|
||||
if (!result) {
|
||||
resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return WOULD_BLOCK;
|
||||
}
|
||||
|
||||
status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
|
||||
Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
|
||||
MotionEvent* motionEvent = factory->createMotionEvent();
|
||||
if (! motionEvent) return NO_MEMORY;
|
||||
|
||||
uint32_t chain = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
InputMessage& msg = batch.samples.editItemAt(i);
|
||||
updateTouchState(&msg);
|
||||
if (i) {
|
||||
SeqChain seqChain;
|
||||
seqChain.seq = msg.body.motion.seq;
|
||||
seqChain.chain = chain;
|
||||
mSeqChains.push(seqChain);
|
||||
addSample(motionEvent, &msg);
|
||||
} else {
|
||||
initializeMotionEvent(motionEvent, &msg);
|
||||
}
|
||||
chain = msg.body.motion.seq;
|
||||
}
|
||||
batch.samples.removeItemsAt(0, count);
|
||||
|
||||
*outSeq = chain;
|
||||
*outEvent = motionEvent;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void InputConsumer::updateTouchState(InputMessage* msg) {
|
||||
if (!mResampleTouch ||
|
||||
!(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t deviceId = msg->body.motion.deviceId;
|
||||
int32_t source = msg->body.motion.source;
|
||||
nsecs_t eventTime = msg->body.motion.eventTime;
|
||||
|
||||
// Update the touch state history to incorporate the new input message.
|
||||
// If the message is in the past relative to the most recently produced resampled
|
||||
// touch, then use the resampled time and coordinates instead.
|
||||
switch (msg->body.motion.action & AMOTION_EVENT_ACTION_MASK) {
|
||||
case AMOTION_EVENT_ACTION_DOWN: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index < 0) {
|
||||
mTouchStates.push();
|
||||
index = mTouchStates.size() - 1;
|
||||
}
|
||||
TouchState& touchState = mTouchStates.editItemAt(index);
|
||||
touchState.initialize(deviceId, source);
|
||||
touchState.addHistory(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
case AMOTION_EVENT_ACTION_MOVE: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index >= 0) {
|
||||
TouchState& touchState = mTouchStates.editItemAt(index);
|
||||
touchState.addHistory(msg);
|
||||
if (eventTime < touchState.lastResample.eventTime) {
|
||||
rewriteMessage(touchState, msg);
|
||||
} else {
|
||||
touchState.lastResample.idBits.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index >= 0) {
|
||||
TouchState& touchState = mTouchStates.editItemAt(index);
|
||||
touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
|
||||
rewriteMessage(touchState, msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index >= 0) {
|
||||
TouchState& touchState = mTouchStates.editItemAt(index);
|
||||
rewriteMessage(touchState, msg);
|
||||
touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AMOTION_EVENT_ACTION_SCROLL: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index >= 0) {
|
||||
const TouchState& touchState = mTouchStates.itemAt(index);
|
||||
rewriteMessage(touchState, msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_CANCEL: {
|
||||
ssize_t index = findTouchState(deviceId, source);
|
||||
if (index >= 0) {
|
||||
const TouchState& touchState = mTouchStates.itemAt(index);
|
||||
rewriteMessage(touchState, msg);
|
||||
mTouchStates.removeAt(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) {
|
||||
for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
|
||||
uint32_t id = msg->body.motion.pointers[i].properties.id;
|
||||
if (state.lastResample.idBits.hasBit(id)) {
|
||||
PointerCoords& msgCoords = msg->body.motion.pointers[i].coords;
|
||||
const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
|
||||
resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
|
||||
resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_Y),
|
||||
msgCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
|
||||
msgCoords.getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
#endif
|
||||
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
|
||||
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
|
||||
const InputMessage* next) {
|
||||
if (!mResampleTouch
|
||||
|| !(event->getSource() & AINPUT_SOURCE_CLASS_POINTER)
|
||||
|| event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
|
||||
if (index < 0) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, no touch state for device.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
TouchState& touchState = mTouchStates.editItemAt(index);
|
||||
if (touchState.historySize < 1) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, no history for device.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the current sample has all of the pointers that need to be reported.
|
||||
const History* current = touchState.getHistory(0);
|
||||
size_t pointerCount = event->getPointerCount();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
uint32_t id = event->getPointerId(i);
|
||||
if (!current->idBits.hasBit(id)) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, missing id %d", id);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the data to use for resampling.
|
||||
const History* other;
|
||||
History future;
|
||||
float alpha;
|
||||
if (next) {
|
||||
// Interpolate between current sample and future sample.
|
||||
// So current->eventTime <= sampleTime <= future.eventTime.
|
||||
future.initializeFrom(next);
|
||||
other = &future;
|
||||
nsecs_t delta = future.eventTime - current->eventTime;
|
||||
if (delta < RESAMPLE_MIN_DELTA) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, delta time is %lld ns.", delta);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
alpha = float(sampleTime - current->eventTime) / delta;
|
||||
} else if (touchState.historySize >= 2) {
|
||||
// Extrapolate future sample using current sample and past sample.
|
||||
// So other->eventTime <= current->eventTime <= sampleTime.
|
||||
other = touchState.getHistory(1);
|
||||
nsecs_t delta = current->eventTime - other->eventTime;
|
||||
if (delta < RESAMPLE_MIN_DELTA) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, delta time is %lld ns.", delta);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
|
||||
if (sampleTime > maxPredict) {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Sample time is too far in the future, adjusting prediction "
|
||||
"from %lld to %lld ns.",
|
||||
sampleTime - current->eventTime, maxPredict - current->eventTime);
|
||||
#endif
|
||||
sampleTime = maxPredict;
|
||||
}
|
||||
alpha = float(current->eventTime - sampleTime) / delta;
|
||||
} else {
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("Not resampled, insufficient data.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Resample touch coordinates.
|
||||
touchState.lastResample.eventTime = sampleTime;
|
||||
touchState.lastResample.idBits.clear();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
uint32_t id = event->getPointerId(i);
|
||||
touchState.lastResample.idToIndex[id] = i;
|
||||
touchState.lastResample.idBits.markBit(id);
|
||||
PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
|
||||
const PointerCoords& currentCoords = current->getPointerById(id);
|
||||
if (other->idBits.hasBit(id)
|
||||
&& shouldResampleTool(event->getToolType(i))) {
|
||||
const PointerCoords& otherCoords = other->getPointerById(id);
|
||||
resampledCoords.copyFrom(currentCoords);
|
||||
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
|
||||
lerp(currentCoords.getX(), otherCoords.getX(), alpha));
|
||||
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
|
||||
lerp(currentCoords.getY(), otherCoords.getY(), alpha));
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
|
||||
"other (%0.3f, %0.3f), alpha %0.3f",
|
||||
id, resampledCoords.getX(), resampledCoords.getY(),
|
||||
currentCoords.getX(), currentCoords.getY(),
|
||||
otherCoords.getX(), otherCoords.getY(),
|
||||
alpha);
|
||||
#endif
|
||||
} else {
|
||||
resampledCoords.copyFrom(currentCoords);
|
||||
#if DEBUG_RESAMPLING
|
||||
ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
|
||||
id, resampledCoords.getX(), resampledCoords.getY(),
|
||||
currentCoords.getX(), currentCoords.getY());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
event->addSample(sampleTime, touchState.lastResample.pointers);
|
||||
}
|
||||
|
||||
bool InputConsumer::shouldResampleTool(int32_t toolType) {
|
||||
return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
|
||||
|| toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
|
||||
#if DEBUG_TRANSPORT_ACTIONS
|
||||
ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
|
||||
mChannel->getName().string(), seq, handled ? "true" : "false");
|
||||
#endif
|
||||
|
||||
if (!seq) {
|
||||
ALOGE("Attempted to send a finished signal with sequence number 0.");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
// Send finished signals for the batch sequence chain first.
|
||||
size_t seqChainCount = mSeqChains.size();
|
||||
if (seqChainCount) {
|
||||
uint32_t currentSeq = seq;
|
||||
uint32_t chainSeqs[seqChainCount];
|
||||
size_t chainIndex = 0;
|
||||
for (size_t i = seqChainCount; i-- > 0; ) {
|
||||
const SeqChain& seqChain = mSeqChains.itemAt(i);
|
||||
if (seqChain.seq == currentSeq) {
|
||||
currentSeq = seqChain.chain;
|
||||
chainSeqs[chainIndex++] = currentSeq;
|
||||
mSeqChains.removeAt(i);
|
||||
}
|
||||
}
|
||||
status_t status = OK;
|
||||
while (!status && chainIndex-- > 0) {
|
||||
status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
|
||||
}
|
||||
if (status) {
|
||||
// An error occurred so at least one signal was not sent, reconstruct the chain.
|
||||
do {
|
||||
SeqChain seqChain;
|
||||
seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
|
||||
seqChain.chain = chainSeqs[chainIndex];
|
||||
mSeqChains.push(seqChain);
|
||||
} while (chainIndex-- > 0);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Send finished signal for the last message in the batch.
|
||||
return sendUnchainedFinishedSignal(seq, handled);
|
||||
}
|
||||
|
||||
status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
|
||||
InputMessage msg;
|
||||
msg.header.type = InputMessage::TYPE_FINISHED;
|
||||
msg.body.finished.seq = seq;
|
||||
msg.body.finished.handled = handled;
|
||||
return mChannel->sendMessage(&msg);
|
||||
}
|
||||
|
||||
bool InputConsumer::hasDeferredEvent() const {
|
||||
return mMsgDeferred;
|
||||
}
|
||||
|
||||
bool InputConsumer::hasPendingBatch() const {
|
||||
return !mBatches.isEmpty();
|
||||
}
|
||||
|
||||
ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
|
||||
for (size_t i = 0; i < mBatches.size(); i++) {
|
||||
const Batch& batch = mBatches.itemAt(i);
|
||||
const InputMessage& head = batch.samples.itemAt(0);
|
||||
if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
|
||||
for (size_t i = 0; i < mTouchStates.size(); i++) {
|
||||
const TouchState& touchState = mTouchStates.itemAt(i);
|
||||
if (touchState.deviceId == deviceId && touchState.source == source) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
|
||||
event->initialize(
|
||||
msg->body.key.deviceId,
|
||||
msg->body.key.source,
|
||||
msg->body.key.action,
|
||||
msg->body.key.flags,
|
||||
msg->body.key.keyCode,
|
||||
msg->body.key.scanCode,
|
||||
msg->body.key.metaState,
|
||||
msg->body.key.repeatCount,
|
||||
msg->body.key.downTime,
|
||||
msg->body.key.eventTime);
|
||||
}
|
||||
|
||||
void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
|
||||
size_t pointerCount = msg->body.motion.pointerCount;
|
||||
PointerProperties pointerProperties[pointerCount];
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
|
||||
pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
|
||||
}
|
||||
|
||||
event->initialize(
|
||||
msg->body.motion.deviceId,
|
||||
msg->body.motion.source,
|
||||
msg->body.motion.action,
|
||||
msg->body.motion.flags,
|
||||
msg->body.motion.edgeFlags,
|
||||
msg->body.motion.metaState,
|
||||
msg->body.motion.buttonState,
|
||||
msg->body.motion.xOffset,
|
||||
msg->body.motion.yOffset,
|
||||
msg->body.motion.xPrecision,
|
||||
msg->body.motion.yPrecision,
|
||||
msg->body.motion.downTime,
|
||||
msg->body.motion.eventTime,
|
||||
pointerCount,
|
||||
pointerProperties,
|
||||
pointerCoords);
|
||||
}
|
||||
|
||||
void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
|
||||
size_t pointerCount = msg->body.motion.pointerCount;
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
|
||||
}
|
||||
|
||||
event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
|
||||
event->addSample(msg->body.motion.eventTime, pointerCoords);
|
||||
}
|
||||
|
||||
bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
|
||||
const InputMessage& head = batch.samples.itemAt(0);
|
||||
size_t pointerCount = msg->body.motion.pointerCount;
|
||||
if (head.body.motion.pointerCount != pointerCount
|
||||
|| head.body.motion.action != msg->body.motion.action) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
if (head.body.motion.pointers[i].properties
|
||||
!= msg->body.motion.pointers[i].properties) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
|
||||
size_t numSamples = batch.samples.size();
|
||||
size_t index = 0;
|
||||
while (index < numSamples
|
||||
&& batch.samples.itemAt(index).body.motion.eventTime <= time) {
|
||||
index += 1;
|
||||
}
|
||||
return ssize_t(index) - 1;
|
||||
}
|
||||
|
||||
} // namespace android
|
1154
libs/input/KeyCharacterMap.cpp
Normal file
1154
libs/input/KeyCharacterMap.cpp
Normal file
File diff suppressed because it is too large
Load Diff
367
libs/input/KeyLayoutMap.cpp
Normal file
367
libs/input/KeyLayoutMap.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "KeyLayoutMap"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <android/keycodes.h>
|
||||
#include <input/Keyboard.h>
|
||||
#include <input/KeyLayoutMap.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Tokenizer.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
// Enables debug output for the parser.
|
||||
#define DEBUG_PARSER 0
|
||||
|
||||
// Enables debug output for parser performance.
|
||||
#define DEBUG_PARSER_PERFORMANCE 0
|
||||
|
||||
// Enables debug output for mapping.
|
||||
#define DEBUG_MAPPING 0
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
static const char* WHITESPACE = " \t\r";
|
||||
|
||||
// --- KeyLayoutMap ---
|
||||
|
||||
KeyLayoutMap::KeyLayoutMap() {
|
||||
}
|
||||
|
||||
KeyLayoutMap::~KeyLayoutMap() {
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
|
||||
outMap->clear();
|
||||
|
||||
Tokenizer* tokenizer;
|
||||
status_t status = Tokenizer::open(filename, &tokenizer);
|
||||
if (status) {
|
||||
ALOGE("Error %d opening key layout map file %s.", status, filename.string());
|
||||
} else {
|
||||
sp<KeyLayoutMap> map = new KeyLayoutMap();
|
||||
if (!map.get()) {
|
||||
ALOGE("Error allocating key layout map.");
|
||||
status = NO_MEMORY;
|
||||
} else {
|
||||
#if DEBUG_PARSER_PERFORMANCE
|
||||
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
#endif
|
||||
Parser parser(map.get(), tokenizer);
|
||||
status = parser.parse();
|
||||
#if DEBUG_PARSER_PERFORMANCE
|
||||
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
|
||||
ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
|
||||
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
|
||||
elapsedTime / 1000000.0);
|
||||
#endif
|
||||
if (!status) {
|
||||
*outMap = map;
|
||||
}
|
||||
}
|
||||
delete tokenizer;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
|
||||
int32_t* outKeyCode, uint32_t* outFlags) const {
|
||||
const Key* key = getKey(scanCode, usageCode);
|
||||
if (!key) {
|
||||
#if DEBUG_MAPPING
|
||||
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
|
||||
#endif
|
||||
*outKeyCode = AKEYCODE_UNKNOWN;
|
||||
*outFlags = 0;
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
*outKeyCode = key->keyCode;
|
||||
*outFlags = key->flags;
|
||||
|
||||
#if DEBUG_MAPPING
|
||||
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
|
||||
scanCode, usageCode, *outKeyCode, *outFlags);
|
||||
#endif
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
|
||||
if (usageCode) {
|
||||
ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
|
||||
if (index >= 0) {
|
||||
return &mKeysByUsageCode.valueAt(index);
|
||||
}
|
||||
}
|
||||
if (scanCode) {
|
||||
ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
|
||||
if (index >= 0) {
|
||||
return &mKeysByScanCode.valueAt(index);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const {
|
||||
const size_t N = mKeysByScanCode.size();
|
||||
for (size_t i=0; i<N; i++) {
|
||||
if (mKeysByScanCode.valueAt(i).keyCode == keyCode) {
|
||||
outScanCodes->add(mKeysByScanCode.keyAt(i));
|
||||
}
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
|
||||
ssize_t index = mAxes.indexOfKey(scanCode);
|
||||
if (index < 0) {
|
||||
#if DEBUG_MAPPING
|
||||
ALOGD("mapAxis: scanCode=%d ~ Failed.", scanCode);
|
||||
#endif
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
*outAxisInfo = mAxes.valueAt(index);
|
||||
|
||||
#if DEBUG_MAPPING
|
||||
ALOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
|
||||
"splitValue=%d, flatOverride=%d.",
|
||||
scanCode,
|
||||
outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
|
||||
outAxisInfo->splitValue, outAxisInfo->flatOverride);
|
||||
#endif
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
// --- KeyLayoutMap::Parser ---
|
||||
|
||||
KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
|
||||
mMap(map), mTokenizer(tokenizer) {
|
||||
}
|
||||
|
||||
KeyLayoutMap::Parser::~Parser() {
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::Parser::parse() {
|
||||
while (!mTokenizer->isEof()) {
|
||||
#if DEBUG_PARSER
|
||||
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
|
||||
mTokenizer->peekRemainderOfLine().string());
|
||||
#endif
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
|
||||
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
|
||||
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
|
||||
if (keywordToken == "key") {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
status_t status = parseKey();
|
||||
if (status) return status;
|
||||
} else if (keywordToken == "axis") {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
status_t status = parseAxis();
|
||||
if (status) return status;
|
||||
} else {
|
||||
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
|
||||
keywordToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
|
||||
ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
|
||||
mTokenizer->getLocation().string(),
|
||||
mTokenizer->peekRemainderOfLine().string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
mTokenizer->nextLine();
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::Parser::parseKey() {
|
||||
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
|
||||
bool mapUsage = false;
|
||||
if (codeToken == "usage") {
|
||||
mapUsage = true;
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
codeToken = mTokenizer->nextToken(WHITESPACE);
|
||||
}
|
||||
|
||||
char* end;
|
||||
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
|
||||
if (*end) {
|
||||
ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
|
||||
mapUsage ? "usage" : "scan code", codeToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
KeyedVector<int32_t, Key>& map =
|
||||
mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
|
||||
if (map.indexOfKey(code) >= 0) {
|
||||
ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
|
||||
mapUsage ? "usage" : "scan code", codeToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
|
||||
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
|
||||
if (!keyCode) {
|
||||
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
|
||||
keyCodeToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
uint32_t flags = 0;
|
||||
for (;;) {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
|
||||
|
||||
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
|
||||
uint32_t flag = getKeyFlagByLabel(flagToken.string());
|
||||
if (!flag) {
|
||||
ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
|
||||
flagToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (flags & flag) {
|
||||
ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
|
||||
flagToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
flags |= flag;
|
||||
}
|
||||
|
||||
#if DEBUG_PARSER
|
||||
ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
|
||||
mapUsage ? "usage" : "scan code", code, keyCode, flags);
|
||||
#endif
|
||||
Key key;
|
||||
key.keyCode = keyCode;
|
||||
key.flags = flags;
|
||||
map.add(code, key);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t KeyLayoutMap::Parser::parseAxis() {
|
||||
String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE);
|
||||
char* end;
|
||||
int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0));
|
||||
if (*end) {
|
||||
ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(),
|
||||
scanCodeToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (mMap->mAxes.indexOfKey(scanCode) >= 0) {
|
||||
ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(),
|
||||
scanCodeToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
AxisInfo axisInfo;
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 token = mTokenizer->nextToken(WHITESPACE);
|
||||
if (token == "invert") {
|
||||
axisInfo.mode = AxisInfo::MODE_INVERT;
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 axisToken = mTokenizer->nextToken(WHITESPACE);
|
||||
axisInfo.axis = getAxisByLabel(axisToken.string());
|
||||
if (axisInfo.axis < 0) {
|
||||
ALOGE("%s: Expected inverted axis label, got '%s'.",
|
||||
mTokenizer->getLocation().string(), axisToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
} else if (token == "split") {
|
||||
axisInfo.mode = AxisInfo::MODE_SPLIT;
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 splitToken = mTokenizer->nextToken(WHITESPACE);
|
||||
axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
|
||||
if (*end) {
|
||||
ALOGE("%s: Expected split value, got '%s'.",
|
||||
mTokenizer->getLocation().string(), splitToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
|
||||
axisInfo.axis = getAxisByLabel(lowAxisToken.string());
|
||||
if (axisInfo.axis < 0) {
|
||||
ALOGE("%s: Expected low axis label, got '%s'.",
|
||||
mTokenizer->getLocation().string(), lowAxisToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
|
||||
axisInfo.highAxis = getAxisByLabel(highAxisToken.string());
|
||||
if (axisInfo.highAxis < 0) {
|
||||
ALOGE("%s: Expected high axis label, got '%s'.",
|
||||
mTokenizer->getLocation().string(), highAxisToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
} else {
|
||||
axisInfo.axis = getAxisByLabel(token.string());
|
||||
if (axisInfo.axis < 0) {
|
||||
ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
|
||||
mTokenizer->getLocation().string(), token.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') {
|
||||
break;
|
||||
}
|
||||
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
|
||||
if (keywordToken == "flat") {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
String8 flatToken = mTokenizer->nextToken(WHITESPACE);
|
||||
axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
|
||||
if (*end) {
|
||||
ALOGE("%s: Expected flat value, got '%s'.",
|
||||
mTokenizer->getLocation().string(), flatToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
} else {
|
||||
ALOGE("%s: Expected keyword 'flat', got '%s'.",
|
||||
mTokenizer->getLocation().string(), keywordToken.string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_PARSER
|
||||
ALOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
|
||||
"splitValue=%d, flatOverride=%d.",
|
||||
scanCode,
|
||||
axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
|
||||
axisInfo.splitValue, axisInfo.flatOverride);
|
||||
#endif
|
||||
mMap->mAxes.add(scanCode, axisInfo);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
};
|
297
libs/input/Keyboard.cpp
Normal file
297
libs/input/Keyboard.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "Keyboard"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <input/Keyboard.h>
|
||||
#include <input/KeycodeLabels.h>
|
||||
#include <input/KeyLayoutMap.h>
|
||||
#include <input/KeyCharacterMap.h>
|
||||
#include <input/InputDevice.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// --- KeyMap ---
|
||||
|
||||
KeyMap::KeyMap() {
|
||||
}
|
||||
|
||||
KeyMap::~KeyMap() {
|
||||
}
|
||||
|
||||
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
|
||||
const PropertyMap* deviceConfiguration) {
|
||||
// Use the configured key layout if available.
|
||||
if (deviceConfiguration) {
|
||||
String8 keyLayoutName;
|
||||
if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
|
||||
keyLayoutName)) {
|
||||
status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
|
||||
if (status == NAME_NOT_FOUND) {
|
||||
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
|
||||
"it was not found.",
|
||||
deviceIdenfifier.name.string(), keyLayoutName.string());
|
||||
}
|
||||
}
|
||||
|
||||
String8 keyCharacterMapName;
|
||||
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
|
||||
keyCharacterMapName)) {
|
||||
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
|
||||
if (status == NAME_NOT_FOUND) {
|
||||
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
|
||||
"map '%s' but it was not found.",
|
||||
deviceIdenfifier.name.string(), keyLayoutName.string());
|
||||
}
|
||||
}
|
||||
|
||||
if (isComplete()) {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Try searching by device identifier.
|
||||
if (probeKeyMap(deviceIdenfifier, String8::empty())) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Fall back on the Generic key map.
|
||||
// TODO Apply some additional heuristics here to figure out what kind of
|
||||
// generic key map to use (US English, etc.) for typical external keyboards.
|
||||
if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Try the Virtual key map as a last resort.
|
||||
if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Give up!
|
||||
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
|
||||
deviceIdenfifier.name.string());
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& keyMapName) {
|
||||
if (!haveKeyLayout()) {
|
||||
loadKeyLayout(deviceIdentifier, keyMapName);
|
||||
}
|
||||
if (!haveKeyCharacterMap()) {
|
||||
loadKeyCharacterMap(deviceIdentifier, keyMapName);
|
||||
}
|
||||
return isComplete();
|
||||
}
|
||||
|
||||
status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& name) {
|
||||
String8 path(getPath(deviceIdentifier, name,
|
||||
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
|
||||
if (path.isEmpty()) {
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
keyLayoutFile.setTo(path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& name) {
|
||||
String8 path(getPath(deviceIdentifier, name,
|
||||
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP));
|
||||
if (path.isEmpty()) {
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
status_t status = KeyCharacterMap::load(path,
|
||||
KeyCharacterMap::FORMAT_BASE, &keyCharacterMap);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
keyCharacterMapFile.setTo(path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const String8& name, InputDeviceConfigurationFileType type) {
|
||||
return name.isEmpty()
|
||||
? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
|
||||
: getInputDeviceConfigurationFilePathByName(name, type);
|
||||
}
|
||||
|
||||
|
||||
// --- Global functions ---
|
||||
|
||||
bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
|
||||
const PropertyMap* deviceConfiguration, const KeyMap* keyMap) {
|
||||
if (!keyMap->haveKeyCharacterMap()
|
||||
|| keyMap->keyCharacterMap->getKeyboardType()
|
||||
== KeyCharacterMap::KEYBOARD_TYPE_SPECIAL_FUNCTION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deviceConfiguration) {
|
||||
bool builtIn = false;
|
||||
if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn)
|
||||
&& builtIn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return strstr(deviceIdentifier.name.string(), "-keypad");
|
||||
}
|
||||
|
||||
static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) {
|
||||
while (list->literal) {
|
||||
if (strcmp(literal, list->literal) == 0) {
|
||||
return list->value;
|
||||
}
|
||||
list++;
|
||||
}
|
||||
return list->value;
|
||||
}
|
||||
|
||||
static const char* lookupLabelByValue(int value, const KeycodeLabel *list) {
|
||||
while (list->literal) {
|
||||
if (list->value == value) {
|
||||
return list->literal;
|
||||
}
|
||||
list++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t getKeyCodeByLabel(const char* label) {
|
||||
return int32_t(lookupValueByLabel(label, KEYCODES));
|
||||
}
|
||||
|
||||
uint32_t getKeyFlagByLabel(const char* label) {
|
||||
return uint32_t(lookupValueByLabel(label, FLAGS));
|
||||
}
|
||||
|
||||
int32_t getAxisByLabel(const char* label) {
|
||||
return int32_t(lookupValueByLabel(label, AXES));
|
||||
}
|
||||
|
||||
const char* getAxisLabel(int32_t axisId) {
|
||||
return lookupLabelByValue(axisId, AXES);
|
||||
}
|
||||
|
||||
static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) {
|
||||
int32_t newMetaState;
|
||||
if (down) {
|
||||
newMetaState = oldMetaState | mask;
|
||||
} else {
|
||||
newMetaState = oldMetaState &
|
||||
~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON);
|
||||
}
|
||||
|
||||
if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
|
||||
newMetaState |= AMETA_ALT_ON;
|
||||
}
|
||||
|
||||
if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
|
||||
newMetaState |= AMETA_SHIFT_ON;
|
||||
}
|
||||
|
||||
if (newMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
|
||||
newMetaState |= AMETA_CTRL_ON;
|
||||
}
|
||||
|
||||
if (newMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
|
||||
newMetaState |= AMETA_META_ON;
|
||||
}
|
||||
return newMetaState;
|
||||
}
|
||||
|
||||
static int32_t toggleLockedMetaState(int32_t mask, bool down, int32_t oldMetaState) {
|
||||
if (down) {
|
||||
return oldMetaState;
|
||||
} else {
|
||||
return oldMetaState ^ mask;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
|
||||
int32_t mask;
|
||||
switch (keyCode) {
|
||||
case AKEYCODE_ALT_LEFT:
|
||||
return setEphemeralMetaState(AMETA_ALT_LEFT_ON, down, oldMetaState);
|
||||
case AKEYCODE_ALT_RIGHT:
|
||||
return setEphemeralMetaState(AMETA_ALT_RIGHT_ON, down, oldMetaState);
|
||||
case AKEYCODE_SHIFT_LEFT:
|
||||
return setEphemeralMetaState(AMETA_SHIFT_LEFT_ON, down, oldMetaState);
|
||||
case AKEYCODE_SHIFT_RIGHT:
|
||||
return setEphemeralMetaState(AMETA_SHIFT_RIGHT_ON, down, oldMetaState);
|
||||
case AKEYCODE_SYM:
|
||||
return setEphemeralMetaState(AMETA_SYM_ON, down, oldMetaState);
|
||||
case AKEYCODE_FUNCTION:
|
||||
return setEphemeralMetaState(AMETA_FUNCTION_ON, down, oldMetaState);
|
||||
case AKEYCODE_CTRL_LEFT:
|
||||
return setEphemeralMetaState(AMETA_CTRL_LEFT_ON, down, oldMetaState);
|
||||
case AKEYCODE_CTRL_RIGHT:
|
||||
return setEphemeralMetaState(AMETA_CTRL_RIGHT_ON, down, oldMetaState);
|
||||
case AKEYCODE_META_LEFT:
|
||||
return setEphemeralMetaState(AMETA_META_LEFT_ON, down, oldMetaState);
|
||||
case AKEYCODE_META_RIGHT:
|
||||
return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState);
|
||||
case AKEYCODE_CAPS_LOCK:
|
||||
return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState);
|
||||
case AKEYCODE_NUM_LOCK:
|
||||
return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState);
|
||||
case AKEYCODE_SCROLL_LOCK:
|
||||
return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState);
|
||||
default:
|
||||
return oldMetaState;
|
||||
}
|
||||
}
|
||||
|
||||
bool isMetaKey(int32_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case AKEYCODE_ALT_LEFT:
|
||||
case AKEYCODE_ALT_RIGHT:
|
||||
case AKEYCODE_SHIFT_LEFT:
|
||||
case AKEYCODE_SHIFT_RIGHT:
|
||||
case AKEYCODE_SYM:
|
||||
case AKEYCODE_FUNCTION:
|
||||
case AKEYCODE_CTRL_LEFT:
|
||||
case AKEYCODE_CTRL_RIGHT:
|
||||
case AKEYCODE_META_LEFT:
|
||||
case AKEYCODE_META_RIGHT:
|
||||
case AKEYCODE_CAPS_LOCK:
|
||||
case AKEYCODE_NUM_LOCK:
|
||||
case AKEYCODE_SCROLL_LOCK:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace android
|
110
libs/input/VelocityControl.cpp
Normal file
110
libs/input/VelocityControl.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VelocityControl"
|
||||
//#define LOG_NDEBUG 0
|
||||
|
||||
// Log debug messages about acceleration.
|
||||
#define DEBUG_ACCELERATION 0
|
||||
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <input/VelocityControl.h>
|
||||
#include <utils/BitSet.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// --- VelocityControl ---
|
||||
|
||||
const nsecs_t VelocityControl::STOP_TIME;
|
||||
|
||||
VelocityControl::VelocityControl() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
|
||||
mParameters = parameters;
|
||||
reset();
|
||||
}
|
||||
|
||||
void VelocityControl::reset() {
|
||||
mLastMovementTime = LLONG_MIN;
|
||||
mRawPosition.x = 0;
|
||||
mRawPosition.y = 0;
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
|
||||
void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
|
||||
if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
|
||||
if (eventTime >= mLastMovementTime + STOP_TIME) {
|
||||
#if DEBUG_ACCELERATION
|
||||
ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
|
||||
(eventTime - mLastMovementTime) * 0.000001f);
|
||||
#endif
|
||||
reset();
|
||||
}
|
||||
|
||||
mLastMovementTime = eventTime;
|
||||
if (deltaX) {
|
||||
mRawPosition.x += *deltaX;
|
||||
}
|
||||
if (deltaY) {
|
||||
mRawPosition.y += *deltaY;
|
||||
}
|
||||
mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), &mRawPosition);
|
||||
|
||||
float vx, vy;
|
||||
float scale = mParameters.scale;
|
||||
if (mVelocityTracker.getVelocity(0, &vx, &vy)) {
|
||||
float speed = hypotf(vx, vy) * scale;
|
||||
if (speed >= mParameters.highThreshold) {
|
||||
// Apply full acceleration above the high speed threshold.
|
||||
scale *= mParameters.acceleration;
|
||||
} else if (speed > mParameters.lowThreshold) {
|
||||
// Linearly interpolate the acceleration to apply between the low and high
|
||||
// speed thresholds.
|
||||
scale *= 1 + (speed - mParameters.lowThreshold)
|
||||
/ (mParameters.highThreshold - mParameters.lowThreshold)
|
||||
* (mParameters.acceleration - 1);
|
||||
}
|
||||
|
||||
#if DEBUG_ACCELERATION
|
||||
ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
|
||||
"vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
|
||||
mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
|
||||
mParameters.acceleration,
|
||||
vx, vy, speed, scale / mParameters.scale);
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG_ACCELERATION
|
||||
ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
|
||||
mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
|
||||
mParameters.acceleration);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (deltaX) {
|
||||
*deltaX *= scale;
|
||||
}
|
||||
if (deltaY) {
|
||||
*deltaY *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
927
libs/input/VelocityTracker.cpp
Normal file
927
libs/input/VelocityTracker.cpp
Normal file
@ -0,0 +1,927 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VelocityTracker"
|
||||
//#define LOG_NDEBUG 0
|
||||
|
||||
// Log debug messages about velocity tracking.
|
||||
#define DEBUG_VELOCITY 0
|
||||
|
||||
// Log debug messages about the progress of the algorithm itself.
|
||||
#define DEBUG_STRATEGY 0
|
||||
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <cutils/properties.h>
|
||||
#include <input/VelocityTracker.h>
|
||||
#include <utils/BitSet.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// Nanoseconds per milliseconds.
|
||||
static const nsecs_t NANOS_PER_MS = 1000000;
|
||||
|
||||
// Threshold for determining that a pointer has stopped moving.
|
||||
// Some input devices do not send ACTION_MOVE events in the case where a pointer has
|
||||
// stopped. We need to detect this case so that we can accurately predict the
|
||||
// velocity after the pointer starts moving again.
|
||||
static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
|
||||
|
||||
|
||||
static float vectorDot(const float* a, const float* b, uint32_t m) {
|
||||
float r = 0;
|
||||
while (m--) {
|
||||
r += *(a++) * *(b++);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static float vectorNorm(const float* a, uint32_t m) {
|
||||
float r = 0;
|
||||
while (m--) {
|
||||
float t = *(a++);
|
||||
r += t * t;
|
||||
}
|
||||
return sqrtf(r);
|
||||
}
|
||||
|
||||
#if DEBUG_STRATEGY || DEBUG_VELOCITY
|
||||
static String8 vectorToString(const float* a, uint32_t m) {
|
||||
String8 str;
|
||||
str.append("[");
|
||||
while (m--) {
|
||||
str.appendFormat(" %f", *(a++));
|
||||
if (m) {
|
||||
str.append(",");
|
||||
}
|
||||
}
|
||||
str.append(" ]");
|
||||
return str;
|
||||
}
|
||||
|
||||
static String8 matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
|
||||
String8 str;
|
||||
str.append("[");
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
if (i) {
|
||||
str.append(",");
|
||||
}
|
||||
str.append(" [");
|
||||
for (size_t j = 0; j < n; j++) {
|
||||
if (j) {
|
||||
str.append(",");
|
||||
}
|
||||
str.appendFormat(" %f", a[rowMajor ? i * n + j : j * m + i]);
|
||||
}
|
||||
str.append(" ]");
|
||||
}
|
||||
str.append(" ]");
|
||||
return str;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// --- VelocityTracker ---
|
||||
|
||||
// The default velocity tracker strategy.
|
||||
// Although other strategies are available for testing and comparison purposes,
|
||||
// this is the strategy that applications will actually use. Be very careful
|
||||
// when adjusting the default strategy because it can dramatically affect
|
||||
// (often in a bad way) the user experience.
|
||||
const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2";
|
||||
|
||||
VelocityTracker::VelocityTracker(const char* strategy) :
|
||||
mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
|
||||
// Allow the default strategy to be overridden using a system property for debugging.
|
||||
if (!strategy) {
|
||||
int length = property_get("debug.velocitytracker.strategy", value, NULL);
|
||||
if (length > 0) {
|
||||
strategy = value;
|
||||
} else {
|
||||
strategy = DEFAULT_STRATEGY;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the strategy.
|
||||
if (!configureStrategy(strategy)) {
|
||||
ALOGD("Unrecognized velocity tracker strategy name '%s'.", strategy);
|
||||
if (!configureStrategy(DEFAULT_STRATEGY)) {
|
||||
LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%s'!",
|
||||
strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VelocityTracker::~VelocityTracker() {
|
||||
delete mStrategy;
|
||||
}
|
||||
|
||||
bool VelocityTracker::configureStrategy(const char* strategy) {
|
||||
mStrategy = createStrategy(strategy);
|
||||
return mStrategy != NULL;
|
||||
}
|
||||
|
||||
VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) {
|
||||
if (!strcmp("lsq1", strategy)) {
|
||||
// 1st order least squares. Quality: POOR.
|
||||
// Frequently underfits the touch data especially when the finger accelerates
|
||||
// or changes direction. Often underestimates velocity. The direction
|
||||
// is overly influenced by historical touch points.
|
||||
return new LeastSquaresVelocityTrackerStrategy(1);
|
||||
}
|
||||
if (!strcmp("lsq2", strategy)) {
|
||||
// 2nd order least squares. Quality: VERY GOOD.
|
||||
// Pretty much ideal, but can be confused by certain kinds of touch data,
|
||||
// particularly if the panel has a tendency to generate delayed,
|
||||
// duplicate or jittery touch coordinates when the finger is released.
|
||||
return new LeastSquaresVelocityTrackerStrategy(2);
|
||||
}
|
||||
if (!strcmp("lsq3", strategy)) {
|
||||
// 3rd order least squares. Quality: UNUSABLE.
|
||||
// Frequently overfits the touch data yielding wildly divergent estimates
|
||||
// of the velocity when the finger is released.
|
||||
return new LeastSquaresVelocityTrackerStrategy(3);
|
||||
}
|
||||
if (!strcmp("wlsq2-delta", strategy)) {
|
||||
// 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
|
||||
return new LeastSquaresVelocityTrackerStrategy(2,
|
||||
LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
|
||||
}
|
||||
if (!strcmp("wlsq2-central", strategy)) {
|
||||
// 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL
|
||||
return new LeastSquaresVelocityTrackerStrategy(2,
|
||||
LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
|
||||
}
|
||||
if (!strcmp("wlsq2-recent", strategy)) {
|
||||
// 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
|
||||
return new LeastSquaresVelocityTrackerStrategy(2,
|
||||
LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
|
||||
}
|
||||
if (!strcmp("int1", strategy)) {
|
||||
// 1st order integrating filter. Quality: GOOD.
|
||||
// Not as good as 'lsq2' because it cannot estimate acceleration but it is
|
||||
// more tolerant of errors. Like 'lsq1', this strategy tends to underestimate
|
||||
// the velocity of a fling but this strategy tends to respond to changes in
|
||||
// direction more quickly and accurately.
|
||||
return new IntegratingVelocityTrackerStrategy(1);
|
||||
}
|
||||
if (!strcmp("int2", strategy)) {
|
||||
// 2nd order integrating filter. Quality: EXPERIMENTAL.
|
||||
// For comparison purposes only. Unlike 'int1' this strategy can compensate
|
||||
// for acceleration but it typically overestimates the effect.
|
||||
return new IntegratingVelocityTrackerStrategy(2);
|
||||
}
|
||||
if (!strcmp("legacy", strategy)) {
|
||||
// Legacy velocity tracker algorithm. Quality: POOR.
|
||||
// For comparison purposes only. This algorithm is strongly influenced by
|
||||
// old data points, consistently underestimates velocity and takes a very long
|
||||
// time to adjust to changes in direction.
|
||||
return new LegacyVelocityTrackerStrategy();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void VelocityTracker::clear() {
|
||||
mCurrentPointerIdBits.clear();
|
||||
mActivePointerId = -1;
|
||||
|
||||
mStrategy->clear();
|
||||
}
|
||||
|
||||
void VelocityTracker::clearPointers(BitSet32 idBits) {
|
||||
BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
|
||||
mCurrentPointerIdBits = remainingIdBits;
|
||||
|
||||
if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
|
||||
mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
|
||||
}
|
||||
|
||||
mStrategy->clearPointers(idBits);
|
||||
}
|
||||
|
||||
void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
|
||||
while (idBits.count() > MAX_POINTERS) {
|
||||
idBits.clearLastMarkedBit();
|
||||
}
|
||||
|
||||
if ((mCurrentPointerIdBits.value & idBits.value)
|
||||
&& eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
|
||||
#if DEBUG_VELOCITY
|
||||
ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
|
||||
(eventTime - mLastEventTime) * 0.000001f);
|
||||
#endif
|
||||
// We have not received any movements for too long. Assume that all pointers
|
||||
// have stopped.
|
||||
mStrategy->clear();
|
||||
}
|
||||
mLastEventTime = eventTime;
|
||||
|
||||
mCurrentPointerIdBits = idBits;
|
||||
if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
|
||||
mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
|
||||
}
|
||||
|
||||
mStrategy->addMovement(eventTime, idBits, positions);
|
||||
|
||||
#if DEBUG_VELOCITY
|
||||
ALOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
|
||||
eventTime, idBits.value, mActivePointerId);
|
||||
for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
|
||||
uint32_t id = iterBits.firstMarkedBit();
|
||||
uint32_t index = idBits.getIndexOfBit(id);
|
||||
iterBits.clearBit(id);
|
||||
Estimator estimator;
|
||||
getEstimator(id, &estimator);
|
||||
ALOGD(" %d: position (%0.3f, %0.3f), "
|
||||
"estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
|
||||
id, positions[index].x, positions[index].y,
|
||||
int(estimator.degree),
|
||||
vectorToString(estimator.xCoeff, estimator.degree + 1).string(),
|
||||
vectorToString(estimator.yCoeff, estimator.degree + 1).string(),
|
||||
estimator.confidence);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VelocityTracker::addMovement(const MotionEvent* event) {
|
||||
int32_t actionMasked = event->getActionMasked();
|
||||
|
||||
switch (actionMasked) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_HOVER_ENTER:
|
||||
// Clear all pointers on down before adding the new movement.
|
||||
clear();
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
|
||||
// Start a new movement trace for a pointer that just went down.
|
||||
// We do this on down instead of on up because the client may want to query the
|
||||
// final velocity for a pointer that just went up.
|
||||
BitSet32 downIdBits;
|
||||
downIdBits.markBit(event->getPointerId(event->getActionIndex()));
|
||||
clearPointers(downIdBits);
|
||||
break;
|
||||
}
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
case AMOTION_EVENT_ACTION_HOVER_MOVE:
|
||||
break;
|
||||
default:
|
||||
// Ignore all other actions because they do not convey any new information about
|
||||
// pointer movement. We also want to preserve the last known velocity of the pointers.
|
||||
// Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
|
||||
// of the pointers that went up. ACTION_POINTER_UP does include the new position of
|
||||
// pointers that remained down but we will also receive an ACTION_MOVE with this
|
||||
// information if any of them actually moved. Since we don't know how many pointers
|
||||
// will be going up at once it makes sense to just wait for the following ACTION_MOVE
|
||||
// before adding the movement.
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pointerCount = event->getPointerCount();
|
||||
if (pointerCount > MAX_POINTERS) {
|
||||
pointerCount = MAX_POINTERS;
|
||||
}
|
||||
|
||||
BitSet32 idBits;
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
idBits.markBit(event->getPointerId(i));
|
||||
}
|
||||
|
||||
uint32_t pointerIndex[MAX_POINTERS];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
|
||||
}
|
||||
|
||||
nsecs_t eventTime;
|
||||
Position positions[pointerCount];
|
||||
|
||||
size_t historySize = event->getHistorySize();
|
||||
for (size_t h = 0; h < historySize; h++) {
|
||||
eventTime = event->getHistoricalEventTime(h);
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
uint32_t index = pointerIndex[i];
|
||||
positions[index].x = event->getHistoricalX(i, h);
|
||||
positions[index].y = event->getHistoricalY(i, h);
|
||||
}
|
||||
addMovement(eventTime, idBits, positions);
|
||||
}
|
||||
|
||||
eventTime = event->getEventTime();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
uint32_t index = pointerIndex[i];
|
||||
positions[index].x = event->getX(i);
|
||||
positions[index].y = event->getY(i);
|
||||
}
|
||||
addMovement(eventTime, idBits, positions);
|
||||
}
|
||||
|
||||
bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
|
||||
Estimator estimator;
|
||||
if (getEstimator(id, &estimator) && estimator.degree >= 1) {
|
||||
*outVx = estimator.xCoeff[1];
|
||||
*outVy = estimator.yCoeff[1];
|
||||
return true;
|
||||
}
|
||||
*outVx = 0;
|
||||
*outVy = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
|
||||
return mStrategy->getEstimator(id, outEstimator);
|
||||
}
|
||||
|
||||
|
||||
// --- LeastSquaresVelocityTrackerStrategy ---
|
||||
|
||||
const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON;
|
||||
const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE;
|
||||
|
||||
LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
|
||||
uint32_t degree, Weighting weighting) :
|
||||
mDegree(degree), mWeighting(weighting) {
|
||||
clear();
|
||||
}
|
||||
|
||||
LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
|
||||
}
|
||||
|
||||
void LeastSquaresVelocityTrackerStrategy::clear() {
|
||||
mIndex = 0;
|
||||
mMovements[0].idBits.clear();
|
||||
}
|
||||
|
||||
void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
|
||||
BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
|
||||
mMovements[mIndex].idBits = remainingIdBits;
|
||||
}
|
||||
|
||||
void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions) {
|
||||
if (++mIndex == HISTORY_SIZE) {
|
||||
mIndex = 0;
|
||||
}
|
||||
|
||||
Movement& movement = mMovements[mIndex];
|
||||
movement.eventTime = eventTime;
|
||||
movement.idBits = idBits;
|
||||
uint32_t count = idBits.count();
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
movement.positions[i] = positions[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solves a linear least squares problem to obtain a N degree polynomial that fits
|
||||
* the specified input data as nearly as possible.
|
||||
*
|
||||
* Returns true if a solution is found, false otherwise.
|
||||
*
|
||||
* The input consists of two vectors of data points X and Y with indices 0..m-1
|
||||
* along with a weight vector W of the same size.
|
||||
*
|
||||
* The output is a vector B with indices 0..n that describes a polynomial
|
||||
* that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
|
||||
* + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
|
||||
*
|
||||
* Accordingly, the weight vector W should be initialized by the caller with the
|
||||
* reciprocal square root of the variance of the error in each input data point.
|
||||
* In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
|
||||
* The weights express the relative importance of each data point. If the weights are
|
||||
* all 1, then the data points are considered to be of equal importance when fitting
|
||||
* the polynomial. It is a good idea to choose weights that diminish the importance
|
||||
* of data points that may have higher than usual error margins.
|
||||
*
|
||||
* Errors among data points are assumed to be independent. W is represented here
|
||||
* as a vector although in the literature it is typically taken to be a diagonal matrix.
|
||||
*
|
||||
* That is to say, the function that generated the input data can be approximated
|
||||
* by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
|
||||
*
|
||||
* The coefficient of determination (R^2) is also returned to describe the goodness
|
||||
* of fit of the model for the given data. It is a value between 0 and 1, where 1
|
||||
* indicates perfect correspondence.
|
||||
*
|
||||
* This function first expands the X vector to a m by n matrix A such that
|
||||
* A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
|
||||
* multiplies it by w[i]./
|
||||
*
|
||||
* Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
|
||||
* and an m by n upper triangular matrix R. Because R is upper triangular (lower
|
||||
* part is all zeroes), we can simplify the decomposition into an m by n matrix
|
||||
* Q1 and a n by n matrix R1 such that A = Q1 R1.
|
||||
*
|
||||
* Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
|
||||
* to find B.
|
||||
*
|
||||
* For efficiency, we lay out A and Q column-wise in memory because we frequently
|
||||
* operate on the column vectors. Conversely, we lay out R row-wise.
|
||||
*
|
||||
* http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
|
||||
* http://en.wikipedia.org/wiki/Gram-Schmidt
|
||||
*/
|
||||
static bool solveLeastSquares(const float* x, const float* y,
|
||||
const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) {
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
|
||||
vectorToString(x, m).string(), vectorToString(y, m).string(),
|
||||
vectorToString(w, m).string());
|
||||
#endif
|
||||
|
||||
// Expand the X vector to a matrix A, pre-multiplied by the weights.
|
||||
float a[n][m]; // column-major order
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
a[0][h] = w[h];
|
||||
for (uint32_t i = 1; i < n; i++) {
|
||||
a[i][h] = a[i - 1][h] * x[h];
|
||||
}
|
||||
}
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string());
|
||||
#endif
|
||||
|
||||
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
|
||||
float q[n][m]; // orthonormal basis, column-major order
|
||||
float r[n][n]; // upper triangular matrix, row-major order
|
||||
for (uint32_t j = 0; j < n; j++) {
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
q[j][h] = a[j][h];
|
||||
}
|
||||
for (uint32_t i = 0; i < j; i++) {
|
||||
float dot = vectorDot(&q[j][0], &q[i][0], m);
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
q[j][h] -= dot * q[i][h];
|
||||
}
|
||||
}
|
||||
|
||||
float norm = vectorNorm(&q[j][0], m);
|
||||
if (norm < 0.000001f) {
|
||||
// vectors are linearly dependent or zero so no solution
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD(" - no solution, norm=%f", norm);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
float invNorm = 1.0f / norm;
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
q[j][h] *= invNorm;
|
||||
}
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
|
||||
}
|
||||
}
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string());
|
||||
ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string());
|
||||
|
||||
// calculate QR, if we factored A correctly then QR should equal A
|
||||
float qr[n][m];
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
qr[i][h] = 0;
|
||||
for (uint32_t j = 0; j < n; j++) {
|
||||
qr[i][h] += q[j][h] * r[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string());
|
||||
#endif
|
||||
|
||||
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
|
||||
// We just work from bottom-right to top-left calculating B's coefficients.
|
||||
float wy[m];
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
wy[h] = y[h] * w[h];
|
||||
}
|
||||
for (uint32_t i = n; i-- != 0; ) {
|
||||
outB[i] = vectorDot(&q[i][0], wy, m);
|
||||
for (uint32_t j = n - 1; j > i; j--) {
|
||||
outB[i] -= r[i][j] * outB[j];
|
||||
}
|
||||
outB[i] /= r[i][i];
|
||||
}
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD(" - b=%s", vectorToString(outB, n).string());
|
||||
#endif
|
||||
|
||||
// Calculate the coefficient of determination as 1 - (SSerr / SStot) where
|
||||
// SSerr is the residual sum of squares (variance of the error),
|
||||
// and SStot is the total sum of squares (variance of the data) where each
|
||||
// has been weighted.
|
||||
float ymean = 0;
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
ymean += y[h];
|
||||
}
|
||||
ymean /= m;
|
||||
|
||||
float sserr = 0;
|
||||
float sstot = 0;
|
||||
for (uint32_t h = 0; h < m; h++) {
|
||||
float err = y[h] - outB[0];
|
||||
float term = 1;
|
||||
for (uint32_t i = 1; i < n; i++) {
|
||||
term *= x[h];
|
||||
err -= term * outB[i];
|
||||
}
|
||||
sserr += w[h] * w[h] * err * err;
|
||||
float var = y[h] - ymean;
|
||||
sstot += w[h] * w[h] * var * var;
|
||||
}
|
||||
*outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD(" - sserr=%f", sserr);
|
||||
ALOGD(" - sstot=%f", sstot);
|
||||
ALOGD(" - det=%f", *outDet);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
|
||||
VelocityTracker::Estimator* outEstimator) const {
|
||||
outEstimator->clear();
|
||||
|
||||
// Iterate over movement samples in reverse time order and collect samples.
|
||||
float x[HISTORY_SIZE];
|
||||
float y[HISTORY_SIZE];
|
||||
float w[HISTORY_SIZE];
|
||||
float time[HISTORY_SIZE];
|
||||
uint32_t m = 0;
|
||||
uint32_t index = mIndex;
|
||||
const Movement& newestMovement = mMovements[mIndex];
|
||||
do {
|
||||
const Movement& movement = mMovements[index];
|
||||
if (!movement.idBits.hasBit(id)) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsecs_t age = newestMovement.eventTime - movement.eventTime;
|
||||
if (age > HORIZON) {
|
||||
break;
|
||||
}
|
||||
|
||||
const VelocityTracker::Position& position = movement.getPosition(id);
|
||||
x[m] = position.x;
|
||||
y[m] = position.y;
|
||||
w[m] = chooseWeight(index);
|
||||
time[m] = -age * 0.000000001f;
|
||||
index = (index == 0 ? HISTORY_SIZE : index) - 1;
|
||||
} while (++m < HISTORY_SIZE);
|
||||
|
||||
if (m == 0) {
|
||||
return false; // no data
|
||||
}
|
||||
|
||||
// Calculate a least squares polynomial fit.
|
||||
uint32_t degree = mDegree;
|
||||
if (degree > m - 1) {
|
||||
degree = m - 1;
|
||||
}
|
||||
if (degree >= 1) {
|
||||
float xdet, ydet;
|
||||
uint32_t n = degree + 1;
|
||||
if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet)
|
||||
&& solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) {
|
||||
outEstimator->time = newestMovement.eventTime;
|
||||
outEstimator->degree = degree;
|
||||
outEstimator->confidence = xdet * ydet;
|
||||
#if DEBUG_STRATEGY
|
||||
ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
|
||||
int(outEstimator->degree),
|
||||
vectorToString(outEstimator->xCoeff, n).string(),
|
||||
vectorToString(outEstimator->yCoeff, n).string(),
|
||||
outEstimator->confidence);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No velocity data available for this pointer, but we do have its current position.
|
||||
outEstimator->xCoeff[0] = x[0];
|
||||
outEstimator->yCoeff[0] = y[0];
|
||||
outEstimator->time = newestMovement.eventTime;
|
||||
outEstimator->degree = 0;
|
||||
outEstimator->confidence = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
|
||||
switch (mWeighting) {
|
||||
case WEIGHTING_DELTA: {
|
||||
// Weight points based on how much time elapsed between them and the next
|
||||
// point so that points that "cover" a shorter time span are weighed less.
|
||||
// delta 0ms: 0.5
|
||||
// delta 10ms: 1.0
|
||||
if (index == mIndex) {
|
||||
return 1.0f;
|
||||
}
|
||||
uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
|
||||
float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
|
||||
* 0.000001f;
|
||||
if (deltaMillis < 0) {
|
||||
return 0.5f;
|
||||
}
|
||||
if (deltaMillis < 10) {
|
||||
return 0.5f + deltaMillis * 0.05;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
case WEIGHTING_CENTRAL: {
|
||||
// Weight points based on their age, weighing very recent and very old points less.
|
||||
// age 0ms: 0.5
|
||||
// age 10ms: 1.0
|
||||
// age 50ms: 1.0
|
||||
// age 60ms: 0.5
|
||||
float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
|
||||
* 0.000001f;
|
||||
if (ageMillis < 0) {
|
||||
return 0.5f;
|
||||
}
|
||||
if (ageMillis < 10) {
|
||||
return 0.5f + ageMillis * 0.05;
|
||||
}
|
||||
if (ageMillis < 50) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (ageMillis < 60) {
|
||||
return 0.5f + (60 - ageMillis) * 0.05;
|
||||
}
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
case WEIGHTING_RECENT: {
|
||||
// Weight points based on their age, weighing older points less.
|
||||
// age 0ms: 1.0
|
||||
// age 50ms: 1.0
|
||||
// age 100ms: 0.5
|
||||
float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
|
||||
* 0.000001f;
|
||||
if (ageMillis < 50) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (ageMillis < 100) {
|
||||
return 0.5f + (100 - ageMillis) * 0.01f;
|
||||
}
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
case WEIGHTING_NONE:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- IntegratingVelocityTrackerStrategy ---
|
||||
|
||||
IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
|
||||
mDegree(degree) {
|
||||
}
|
||||
|
||||
IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::clear() {
|
||||
mPointerIdBits.clear();
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
|
||||
mPointerIdBits.value &= ~idBits.value;
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions) {
|
||||
uint32_t index = 0;
|
||||
for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
|
||||
uint32_t id = iterIdBits.clearFirstMarkedBit();
|
||||
State& state = mPointerState[id];
|
||||
const VelocityTracker::Position& position = positions[index++];
|
||||
if (mPointerIdBits.hasBit(id)) {
|
||||
updateState(state, eventTime, position.x, position.y);
|
||||
} else {
|
||||
initState(state, eventTime, position.x, position.y);
|
||||
}
|
||||
}
|
||||
|
||||
mPointerIdBits = idBits;
|
||||
}
|
||||
|
||||
bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
|
||||
VelocityTracker::Estimator* outEstimator) const {
|
||||
outEstimator->clear();
|
||||
|
||||
if (mPointerIdBits.hasBit(id)) {
|
||||
const State& state = mPointerState[id];
|
||||
populateEstimator(state, outEstimator);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::initState(State& state,
|
||||
nsecs_t eventTime, float xpos, float ypos) const {
|
||||
state.updateTime = eventTime;
|
||||
state.degree = 0;
|
||||
|
||||
state.xpos = xpos;
|
||||
state.xvel = 0;
|
||||
state.xaccel = 0;
|
||||
state.ypos = ypos;
|
||||
state.yvel = 0;
|
||||
state.yaccel = 0;
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::updateState(State& state,
|
||||
nsecs_t eventTime, float xpos, float ypos) const {
|
||||
const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
|
||||
const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
|
||||
|
||||
if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
|
||||
return;
|
||||
}
|
||||
|
||||
float dt = (eventTime - state.updateTime) * 0.000000001f;
|
||||
state.updateTime = eventTime;
|
||||
|
||||
float xvel = (xpos - state.xpos) / dt;
|
||||
float yvel = (ypos - state.ypos) / dt;
|
||||
if (state.degree == 0) {
|
||||
state.xvel = xvel;
|
||||
state.yvel = yvel;
|
||||
state.degree = 1;
|
||||
} else {
|
||||
float alpha = dt / (FILTER_TIME_CONSTANT + dt);
|
||||
if (mDegree == 1) {
|
||||
state.xvel += (xvel - state.xvel) * alpha;
|
||||
state.yvel += (yvel - state.yvel) * alpha;
|
||||
} else {
|
||||
float xaccel = (xvel - state.xvel) / dt;
|
||||
float yaccel = (yvel - state.yvel) / dt;
|
||||
if (state.degree == 1) {
|
||||
state.xaccel = xaccel;
|
||||
state.yaccel = yaccel;
|
||||
state.degree = 2;
|
||||
} else {
|
||||
state.xaccel += (xaccel - state.xaccel) * alpha;
|
||||
state.yaccel += (yaccel - state.yaccel) * alpha;
|
||||
}
|
||||
state.xvel += (state.xaccel * dt) * alpha;
|
||||
state.yvel += (state.yaccel * dt) * alpha;
|
||||
}
|
||||
}
|
||||
state.xpos = xpos;
|
||||
state.ypos = ypos;
|
||||
}
|
||||
|
||||
void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
|
||||
VelocityTracker::Estimator* outEstimator) const {
|
||||
outEstimator->time = state.updateTime;
|
||||
outEstimator->confidence = 1.0f;
|
||||
outEstimator->degree = state.degree;
|
||||
outEstimator->xCoeff[0] = state.xpos;
|
||||
outEstimator->xCoeff[1] = state.xvel;
|
||||
outEstimator->xCoeff[2] = state.xaccel / 2;
|
||||
outEstimator->yCoeff[0] = state.ypos;
|
||||
outEstimator->yCoeff[1] = state.yvel;
|
||||
outEstimator->yCoeff[2] = state.yaccel / 2;
|
||||
}
|
||||
|
||||
|
||||
// --- LegacyVelocityTrackerStrategy ---
|
||||
|
||||
const nsecs_t LegacyVelocityTrackerStrategy::HORIZON;
|
||||
const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE;
|
||||
const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION;
|
||||
|
||||
LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
|
||||
clear();
|
||||
}
|
||||
|
||||
LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
|
||||
}
|
||||
|
||||
void LegacyVelocityTrackerStrategy::clear() {
|
||||
mIndex = 0;
|
||||
mMovements[0].idBits.clear();
|
||||
}
|
||||
|
||||
void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
|
||||
BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
|
||||
mMovements[mIndex].idBits = remainingIdBits;
|
||||
}
|
||||
|
||||
void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
|
||||
const VelocityTracker::Position* positions) {
|
||||
if (++mIndex == HISTORY_SIZE) {
|
||||
mIndex = 0;
|
||||
}
|
||||
|
||||
Movement& movement = mMovements[mIndex];
|
||||
movement.eventTime = eventTime;
|
||||
movement.idBits = idBits;
|
||||
uint32_t count = idBits.count();
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
movement.positions[i] = positions[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
|
||||
VelocityTracker::Estimator* outEstimator) const {
|
||||
outEstimator->clear();
|
||||
|
||||
const Movement& newestMovement = mMovements[mIndex];
|
||||
if (!newestMovement.idBits.hasBit(id)) {
|
||||
return false; // no data
|
||||
}
|
||||
|
||||
// Find the oldest sample that contains the pointer and that is not older than HORIZON.
|
||||
nsecs_t minTime = newestMovement.eventTime - HORIZON;
|
||||
uint32_t oldestIndex = mIndex;
|
||||
uint32_t numTouches = 1;
|
||||
do {
|
||||
uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
|
||||
const Movement& nextOldestMovement = mMovements[nextOldestIndex];
|
||||
if (!nextOldestMovement.idBits.hasBit(id)
|
||||
|| nextOldestMovement.eventTime < minTime) {
|
||||
break;
|
||||
}
|
||||
oldestIndex = nextOldestIndex;
|
||||
} while (++numTouches < HISTORY_SIZE);
|
||||
|
||||
// Calculate an exponentially weighted moving average of the velocity estimate
|
||||
// at different points in time measured relative to the oldest sample.
|
||||
// This is essentially an IIR filter. Newer samples are weighted more heavily
|
||||
// than older samples. Samples at equal time points are weighted more or less
|
||||
// equally.
|
||||
//
|
||||
// One tricky problem is that the sample data may be poorly conditioned.
|
||||
// Sometimes samples arrive very close together in time which can cause us to
|
||||
// overestimate the velocity at that time point. Most samples might be measured
|
||||
// 16ms apart but some consecutive samples could be only 0.5sm apart because
|
||||
// the hardware or driver reports them irregularly or in bursts.
|
||||
float accumVx = 0;
|
||||
float accumVy = 0;
|
||||
uint32_t index = oldestIndex;
|
||||
uint32_t samplesUsed = 0;
|
||||
const Movement& oldestMovement = mMovements[oldestIndex];
|
||||
const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
|
||||
nsecs_t lastDuration = 0;
|
||||
|
||||
while (numTouches-- > 1) {
|
||||
if (++index == HISTORY_SIZE) {
|
||||
index = 0;
|
||||
}
|
||||
const Movement& movement = mMovements[index];
|
||||
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
|
||||
|
||||
// If the duration between samples is small, we may significantly overestimate
|
||||
// the velocity. Consequently, we impose a minimum duration constraint on the
|
||||
// samples that we include in the calculation.
|
||||
if (duration >= MIN_DURATION) {
|
||||
const VelocityTracker::Position& position = movement.getPosition(id);
|
||||
float scale = 1000000000.0f / duration; // one over time delta in seconds
|
||||
float vx = (position.x - oldestPosition.x) * scale;
|
||||
float vy = (position.y - oldestPosition.y) * scale;
|
||||
accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
|
||||
accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
|
||||
lastDuration = duration;
|
||||
samplesUsed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Report velocity.
|
||||
const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
|
||||
outEstimator->time = newestMovement.eventTime;
|
||||
outEstimator->confidence = 1;
|
||||
outEstimator->xCoeff[0] = newestPosition.x;
|
||||
outEstimator->yCoeff[0] = newestPosition.y;
|
||||
if (samplesUsed) {
|
||||
outEstimator->xCoeff[1] = accumVx;
|
||||
outEstimator->yCoeff[1] = accumVy;
|
||||
outEstimator->degree = 1;
|
||||
} else {
|
||||
outEstimator->degree = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android
|
172
libs/input/VirtualKeyMap.cpp
Normal file
172
libs/input/VirtualKeyMap.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VirtualKeyMap"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <input/VirtualKeyMap.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Tokenizer.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
// Enables debug output for the parser.
|
||||
#define DEBUG_PARSER 0
|
||||
|
||||
// Enables debug output for parser performance.
|
||||
#define DEBUG_PARSER_PERFORMANCE 0
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
static const char* WHITESPACE = " \t\r";
|
||||
static const char* WHITESPACE_OR_FIELD_DELIMITER = " \t\r:";
|
||||
|
||||
|
||||
// --- VirtualKeyMap ---
|
||||
|
||||
VirtualKeyMap::VirtualKeyMap() {
|
||||
}
|
||||
|
||||
VirtualKeyMap::~VirtualKeyMap() {
|
||||
}
|
||||
|
||||
status_t VirtualKeyMap::load(const String8& filename, VirtualKeyMap** outMap) {
|
||||
*outMap = NULL;
|
||||
|
||||
Tokenizer* tokenizer;
|
||||
status_t status = Tokenizer::open(filename, &tokenizer);
|
||||
if (status) {
|
||||
ALOGE("Error %d opening virtual key map file %s.", status, filename.string());
|
||||
} else {
|
||||
VirtualKeyMap* map = new VirtualKeyMap();
|
||||
if (!map) {
|
||||
ALOGE("Error allocating virtual key map.");
|
||||
status = NO_MEMORY;
|
||||
} else {
|
||||
#if DEBUG_PARSER_PERFORMANCE
|
||||
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
#endif
|
||||
Parser parser(map, tokenizer);
|
||||
status = parser.parse();
|
||||
#if DEBUG_PARSER_PERFORMANCE
|
||||
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
|
||||
ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
|
||||
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
|
||||
elapsedTime / 1000000.0);
|
||||
#endif
|
||||
if (status) {
|
||||
delete map;
|
||||
} else {
|
||||
*outMap = map;
|
||||
}
|
||||
}
|
||||
delete tokenizer;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// --- VirtualKeyMap::Parser ---
|
||||
|
||||
VirtualKeyMap::Parser::Parser(VirtualKeyMap* map, Tokenizer* tokenizer) :
|
||||
mMap(map), mTokenizer(tokenizer) {
|
||||
}
|
||||
|
||||
VirtualKeyMap::Parser::~Parser() {
|
||||
}
|
||||
|
||||
status_t VirtualKeyMap::Parser::parse() {
|
||||
while (!mTokenizer->isEof()) {
|
||||
#if DEBUG_PARSER
|
||||
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
|
||||
mTokenizer->peekRemainderOfLine().string());
|
||||
#endif
|
||||
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
|
||||
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
|
||||
// Multiple keys can appear on one line or they can be broken up across multiple lines.
|
||||
do {
|
||||
String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
|
||||
if (token != "0x01") {
|
||||
ALOGE("%s: Unknown virtual key type, expected 0x01.",
|
||||
mTokenizer->getLocation().string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
VirtualKeyDefinition defn;
|
||||
bool success = parseNextIntField(&defn.scanCode)
|
||||
&& parseNextIntField(&defn.centerX)
|
||||
&& parseNextIntField(&defn.centerY)
|
||||
&& parseNextIntField(&defn.width)
|
||||
&& parseNextIntField(&defn.height);
|
||||
if (!success) {
|
||||
ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.",
|
||||
mTokenizer->getLocation().string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
#if DEBUG_PARSER
|
||||
ALOGD("Parsed virtual key: scanCode=%d, centerX=%d, centerY=%d, "
|
||||
"width=%d, height=%d",
|
||||
defn.scanCode, defn.centerX, defn.centerY, defn.width, defn.height);
|
||||
#endif
|
||||
mMap->mVirtualKeys.push(defn);
|
||||
} while (consumeFieldDelimiterAndSkipWhitespace());
|
||||
|
||||
if (!mTokenizer->isEol()) {
|
||||
ALOGE("%s: Expected end of line, got '%s'.",
|
||||
mTokenizer->getLocation().string(),
|
||||
mTokenizer->peekRemainderOfLine().string());
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
mTokenizer->nextLine();
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool VirtualKeyMap::Parser::consumeFieldDelimiterAndSkipWhitespace() {
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
if (mTokenizer->peekChar() == ':') {
|
||||
mTokenizer->nextChar();
|
||||
mTokenizer->skipDelimiters(WHITESPACE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) {
|
||||
if (!consumeFieldDelimiterAndSkipWhitespace()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
|
||||
char* end;
|
||||
*outValue = strtol(token.string(), &end, 0);
|
||||
if (token.isEmpty() || *end != '\0') {
|
||||
ALOGE("Expected an integer, got '%s'.", token.string());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android
|
34
libs/input/tests/Android.mk
Normal file
34
libs/input/tests/Android.mk
Normal file
@ -0,0 +1,34 @@
|
||||
# Build the unit tests.
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# Build the unit tests.
|
||||
test_src_files := \
|
||||
InputChannel_test.cpp \
|
||||
InputEvent_test.cpp \
|
||||
InputPublisherAndConsumer_test.cpp
|
||||
|
||||
shared_libraries := \
|
||||
libinput \
|
||||
libcutils \
|
||||
libutils \
|
||||
libbinder \
|
||||
libui \
|
||||
libstlport \
|
||||
libskia
|
||||
|
||||
static_libraries := \
|
||||
libgtest \
|
||||
libgtest_main
|
||||
|
||||
$(foreach file,$(test_src_files), \
|
||||
$(eval include $(CLEAR_VARS)) \
|
||||
$(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
|
||||
$(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
|
||||
$(eval LOCAL_SRC_FILES := $(file)) \
|
||||
$(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
|
||||
$(eval include $(BUILD_NATIVE_TEST)) \
|
||||
)
|
||||
|
||||
# Build the manual test programs.
|
||||
include $(call all-makefiles-under, $(LOCAL_PATH))
|
159
libs/input/tests/InputChannel_test.cpp
Normal file
159
libs/input/tests/InputChannel_test.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#include "TestHelpers.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <input/InputTransport.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/StopWatch.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class InputChannelTest : public testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() { }
|
||||
virtual void TearDown() { }
|
||||
};
|
||||
|
||||
|
||||
TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) {
|
||||
// Our purpose here is to verify that the input channel destructor closes the
|
||||
// file descriptor provided to it. One easy way is to provide it with one end
|
||||
// of a pipe and to check for EPIPE on the other end after the channel is destroyed.
|
||||
Pipe pipe;
|
||||
|
||||
sp<InputChannel> inputChannel = new InputChannel(String8("channel name"), pipe.sendFd);
|
||||
|
||||
EXPECT_STREQ("channel name", inputChannel->getName().string())
|
||||
<< "channel should have provided name";
|
||||
EXPECT_EQ(pipe.sendFd, inputChannel->getFd())
|
||||
<< "channel should have provided fd";
|
||||
|
||||
inputChannel.clear(); // destroys input channel
|
||||
|
||||
EXPECT_EQ(-EPIPE, pipe.readSignal())
|
||||
<< "channel should have closed fd when destroyed";
|
||||
|
||||
// clean up fds of Pipe endpoints that were closed so we don't try to close them again
|
||||
pipe.sendFd = -1;
|
||||
}
|
||||
|
||||
TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) {
|
||||
sp<InputChannel> serverChannel, clientChannel;
|
||||
|
||||
status_t result = InputChannel::openInputChannelPair(String8("channel name"),
|
||||
serverChannel, clientChannel);
|
||||
|
||||
ASSERT_EQ(OK, result)
|
||||
<< "should have successfully opened a channel pair";
|
||||
|
||||
// Name
|
||||
EXPECT_STREQ("channel name (server)", serverChannel->getName().string())
|
||||
<< "server channel should have suffixed name";
|
||||
EXPECT_STREQ("channel name (client)", clientChannel->getName().string())
|
||||
<< "client channel should have suffixed name";
|
||||
|
||||
// Server->Client communication
|
||||
InputMessage serverMsg;
|
||||
memset(&serverMsg, 0, sizeof(InputMessage));
|
||||
serverMsg.header.type = InputMessage::TYPE_KEY;
|
||||
serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN;
|
||||
EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
|
||||
<< "server channel should be able to send message to client channel";
|
||||
|
||||
InputMessage clientMsg;
|
||||
EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg))
|
||||
<< "client channel should be able to receive message from server channel";
|
||||
EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
|
||||
<< "client channel should receive the correct message from server channel";
|
||||
EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
|
||||
<< "client channel should receive the correct message from server channel";
|
||||
|
||||
// Client->Server communication
|
||||
InputMessage clientReply;
|
||||
memset(&clientReply, 0, sizeof(InputMessage));
|
||||
clientReply.header.type = InputMessage::TYPE_FINISHED;
|
||||
clientReply.body.finished.seq = 0x11223344;
|
||||
clientReply.body.finished.handled = true;
|
||||
EXPECT_EQ(OK, clientChannel->sendMessage(&clientReply))
|
||||
<< "client channel should be able to send message to server channel";
|
||||
|
||||
InputMessage serverReply;
|
||||
EXPECT_EQ(OK, serverChannel->receiveMessage(&serverReply))
|
||||
<< "server channel should be able to receive message from client channel";
|
||||
EXPECT_EQ(clientReply.header.type, serverReply.header.type)
|
||||
<< "server channel should receive the correct message from client channel";
|
||||
EXPECT_EQ(clientReply.body.finished.seq, serverReply.body.finished.seq)
|
||||
<< "server channel should receive the correct message from client channel";
|
||||
EXPECT_EQ(clientReply.body.finished.handled, serverReply.body.finished.handled)
|
||||
<< "server channel should receive the correct message from client channel";
|
||||
}
|
||||
|
||||
TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) {
|
||||
sp<InputChannel> serverChannel, clientChannel;
|
||||
|
||||
status_t result = InputChannel::openInputChannelPair(String8("channel name"),
|
||||
serverChannel, clientChannel);
|
||||
|
||||
ASSERT_EQ(OK, result)
|
||||
<< "should have successfully opened a channel pair";
|
||||
|
||||
InputMessage msg;
|
||||
EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveMessage(&msg))
|
||||
<< "receiveMessage should have returned WOULD_BLOCK";
|
||||
}
|
||||
|
||||
TEST_F(InputChannelTest, ReceiveSignal_WhenPeerClosed_ReturnsAnError) {
|
||||
sp<InputChannel> serverChannel, clientChannel;
|
||||
|
||||
status_t result = InputChannel::openInputChannelPair(String8("channel name"),
|
||||
serverChannel, clientChannel);
|
||||
|
||||
ASSERT_EQ(OK, result)
|
||||
<< "should have successfully opened a channel pair";
|
||||
|
||||
serverChannel.clear(); // close server channel
|
||||
|
||||
InputMessage msg;
|
||||
EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveMessage(&msg))
|
||||
<< "receiveMessage should have returned DEAD_OBJECT";
|
||||
}
|
||||
|
||||
TEST_F(InputChannelTest, SendSignal_WhenPeerClosed_ReturnsAnError) {
|
||||
sp<InputChannel> serverChannel, clientChannel;
|
||||
|
||||
status_t result = InputChannel::openInputChannelPair(String8("channel name"),
|
||||
serverChannel, clientChannel);
|
||||
|
||||
ASSERT_EQ(OK, result)
|
||||
<< "should have successfully opened a channel pair";
|
||||
|
||||
serverChannel.clear(); // close server channel
|
||||
|
||||
InputMessage msg;
|
||||
msg.header.type = InputMessage::TYPE_KEY;
|
||||
EXPECT_EQ(DEAD_OBJECT, clientChannel->sendMessage(&msg))
|
||||
<< "sendMessage should have returned DEAD_OBJECT";
|
||||
}
|
||||
|
||||
|
||||
} // namespace android
|
581
libs/input/tests/InputEvent_test.cpp
Normal file
581
libs/input/tests/InputEvent_test.cpp
Normal file
@ -0,0 +1,581 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <binder/Parcel.h>
|
||||
#include <core/SkMatrix.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <input/Input.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class BaseTest : public testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() { }
|
||||
virtual void TearDown() { }
|
||||
};
|
||||
|
||||
// --- PointerCoordsTest ---
|
||||
|
||||
class PointerCoordsTest : public BaseTest {
|
||||
};
|
||||
|
||||
TEST_F(PointerCoordsTest, ClearSetsBitsToZero) {
|
||||
PointerCoords coords;
|
||||
coords.clear();
|
||||
|
||||
ASSERT_EQ(0ULL, coords.bits);
|
||||
}
|
||||
|
||||
TEST_F(PointerCoordsTest, AxisValues) {
|
||||
float* valuePtr;
|
||||
PointerCoords coords;
|
||||
coords.clear();
|
||||
|
||||
// Check invariants when no axes are present.
|
||||
ASSERT_EQ(0, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
ASSERT_EQ(0, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
|
||||
// Set first axis.
|
||||
ASSERT_EQ(OK, coords.setAxisValue(1, 5));
|
||||
ASSERT_EQ(0x00000002ULL, coords.bits);
|
||||
ASSERT_EQ(5, coords.values[0]);
|
||||
|
||||
ASSERT_EQ(0, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
ASSERT_EQ(5, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return value of axis";
|
||||
|
||||
// Set an axis with a higher id than all others. (appending value at the end)
|
||||
ASSERT_EQ(OK, coords.setAxisValue(3, 2));
|
||||
ASSERT_EQ(0x0000000aULL, coords.bits);
|
||||
ASSERT_EQ(5, coords.values[0]);
|
||||
ASSERT_EQ(2, coords.values[1]);
|
||||
|
||||
ASSERT_EQ(0, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
ASSERT_EQ(5, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(0, coords.getAxisValue(2))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
ASSERT_EQ(2, coords.getAxisValue(3))
|
||||
<< "getAxisValue should return value of axis";
|
||||
|
||||
// Set an axis with an id lower than all others. (prepending value at beginning)
|
||||
ASSERT_EQ(OK, coords.setAxisValue(0, 4));
|
||||
ASSERT_EQ(0x0000000bULL, coords.bits);
|
||||
ASSERT_EQ(4, coords.values[0]);
|
||||
ASSERT_EQ(5, coords.values[1]);
|
||||
ASSERT_EQ(2, coords.values[2]);
|
||||
|
||||
ASSERT_EQ(4, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(5, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(0, coords.getAxisValue(2))
|
||||
<< "getAxisValue should return zero because axis is not present";
|
||||
ASSERT_EQ(2, coords.getAxisValue(3))
|
||||
<< "getAxisValue should return value of axis";
|
||||
|
||||
// Set an axis with an id between the others. (inserting value in the middle)
|
||||
ASSERT_EQ(OK, coords.setAxisValue(2, 1));
|
||||
ASSERT_EQ(0x0000000fULL, coords.bits);
|
||||
ASSERT_EQ(4, coords.values[0]);
|
||||
ASSERT_EQ(5, coords.values[1]);
|
||||
ASSERT_EQ(1, coords.values[2]);
|
||||
ASSERT_EQ(2, coords.values[3]);
|
||||
|
||||
ASSERT_EQ(4, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(5, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(1, coords.getAxisValue(2))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(2, coords.getAxisValue(3))
|
||||
<< "getAxisValue should return value of axis";
|
||||
|
||||
// Set an existing axis value in place.
|
||||
ASSERT_EQ(OK, coords.setAxisValue(1, 6));
|
||||
ASSERT_EQ(0x0000000fULL, coords.bits);
|
||||
ASSERT_EQ(4, coords.values[0]);
|
||||
ASSERT_EQ(6, coords.values[1]);
|
||||
ASSERT_EQ(1, coords.values[2]);
|
||||
ASSERT_EQ(2, coords.values[3]);
|
||||
|
||||
ASSERT_EQ(4, coords.getAxisValue(0))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(6, coords.getAxisValue(1))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(1, coords.getAxisValue(2))
|
||||
<< "getAxisValue should return value of axis";
|
||||
ASSERT_EQ(2, coords.getAxisValue(3))
|
||||
<< "getAxisValue should return value of axis";
|
||||
|
||||
// Set maximum number of axes.
|
||||
for (size_t axis = 4; axis < PointerCoords::MAX_AXES; axis++) {
|
||||
ASSERT_EQ(OK, coords.setAxisValue(axis, axis));
|
||||
}
|
||||
ASSERT_EQ(PointerCoords::MAX_AXES, __builtin_popcountll(coords.bits));
|
||||
|
||||
// Try to set one more axis beyond maximum number.
|
||||
// Ensure bits are unchanged.
|
||||
ASSERT_EQ(NO_MEMORY, coords.setAxisValue(PointerCoords::MAX_AXES, 100));
|
||||
ASSERT_EQ(PointerCoords::MAX_AXES, __builtin_popcountll(coords.bits));
|
||||
}
|
||||
|
||||
TEST_F(PointerCoordsTest, Parcel) {
|
||||
Parcel parcel;
|
||||
|
||||
PointerCoords inCoords;
|
||||
inCoords.clear();
|
||||
PointerCoords outCoords;
|
||||
|
||||
// Round trip with empty coords.
|
||||
inCoords.writeToParcel(&parcel);
|
||||
parcel.setDataPosition(0);
|
||||
outCoords.readFromParcel(&parcel);
|
||||
|
||||
ASSERT_EQ(0ULL, outCoords.bits);
|
||||
|
||||
// Round trip with some values.
|
||||
parcel.freeData();
|
||||
inCoords.setAxisValue(2, 5);
|
||||
inCoords.setAxisValue(5, 8);
|
||||
|
||||
inCoords.writeToParcel(&parcel);
|
||||
parcel.setDataPosition(0);
|
||||
outCoords.readFromParcel(&parcel);
|
||||
|
||||
ASSERT_EQ(outCoords.bits, inCoords.bits);
|
||||
ASSERT_EQ(outCoords.values[0], inCoords.values[0]);
|
||||
ASSERT_EQ(outCoords.values[1], inCoords.values[1]);
|
||||
}
|
||||
|
||||
|
||||
// --- KeyEventTest ---
|
||||
|
||||
class KeyEventTest : public BaseTest {
|
||||
};
|
||||
|
||||
TEST_F(KeyEventTest, Properties) {
|
||||
KeyEvent event;
|
||||
|
||||
// Initialize and get properties.
|
||||
const nsecs_t ARBITRARY_DOWN_TIME = 1;
|
||||
const nsecs_t ARBITRARY_EVENT_TIME = 2;
|
||||
event.initialize(2, AINPUT_SOURCE_GAMEPAD, AKEY_EVENT_ACTION_DOWN,
|
||||
AKEY_EVENT_FLAG_FROM_SYSTEM, AKEYCODE_BUTTON_X, 121,
|
||||
AMETA_ALT_ON, 1, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME);
|
||||
|
||||
ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event.getType());
|
||||
ASSERT_EQ(2, event.getDeviceId());
|
||||
ASSERT_EQ(AINPUT_SOURCE_GAMEPAD, event.getSource());
|
||||
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, event.getAction());
|
||||
ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, event.getFlags());
|
||||
ASSERT_EQ(AKEYCODE_BUTTON_X, event.getKeyCode());
|
||||
ASSERT_EQ(121, event.getScanCode());
|
||||
ASSERT_EQ(AMETA_ALT_ON, event.getMetaState());
|
||||
ASSERT_EQ(1, event.getRepeatCount());
|
||||
ASSERT_EQ(ARBITRARY_DOWN_TIME, event.getDownTime());
|
||||
ASSERT_EQ(ARBITRARY_EVENT_TIME, event.getEventTime());
|
||||
|
||||
// Set source.
|
||||
event.setSource(AINPUT_SOURCE_JOYSTICK);
|
||||
ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
|
||||
}
|
||||
|
||||
|
||||
// --- MotionEventTest ---
|
||||
|
||||
class MotionEventTest : public BaseTest {
|
||||
protected:
|
||||
static const nsecs_t ARBITRARY_DOWN_TIME;
|
||||
static const nsecs_t ARBITRARY_EVENT_TIME;
|
||||
static const float X_OFFSET;
|
||||
static const float Y_OFFSET;
|
||||
|
||||
void initializeEventWithHistory(MotionEvent* event);
|
||||
void assertEqualsEventWithHistory(const MotionEvent* event);
|
||||
};
|
||||
|
||||
const nsecs_t MotionEventTest::ARBITRARY_DOWN_TIME = 1;
|
||||
const nsecs_t MotionEventTest::ARBITRARY_EVENT_TIME = 2;
|
||||
const float MotionEventTest::X_OFFSET = 1.0f;
|
||||
const float MotionEventTest::Y_OFFSET = 1.1f;
|
||||
|
||||
void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
|
||||
PointerProperties pointerProperties[2];
|
||||
pointerProperties[0].clear();
|
||||
pointerProperties[0].id = 1;
|
||||
pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
|
||||
pointerProperties[1].clear();
|
||||
pointerProperties[1].id = 2;
|
||||
pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
|
||||
|
||||
PointerCoords pointerCoords[2];
|
||||
pointerCoords[0].clear();
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
|
||||
pointerCoords[1].clear();
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28);
|
||||
event->initialize(2, AINPUT_SOURCE_TOUCHSCREEN, AMOTION_EVENT_ACTION_MOVE,
|
||||
AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
|
||||
AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
|
||||
X_OFFSET, Y_OFFSET, 2.0f, 2.1f,
|
||||
ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME,
|
||||
2, pointerProperties, pointerCoords);
|
||||
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
|
||||
event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords);
|
||||
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227);
|
||||
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228);
|
||||
event->addSample(ARBITRARY_EVENT_TIME + 2, pointerCoords);
|
||||
}
|
||||
|
||||
void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) {
|
||||
// Check properties.
|
||||
ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
|
||||
ASSERT_EQ(2, event->getDeviceId());
|
||||
ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, event->getSource());
|
||||
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, event->getAction());
|
||||
ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, event->getFlags());
|
||||
ASSERT_EQ(AMOTION_EVENT_EDGE_FLAG_TOP, event->getEdgeFlags());
|
||||
ASSERT_EQ(AMETA_ALT_ON, event->getMetaState());
|
||||
ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState());
|
||||
ASSERT_EQ(X_OFFSET, event->getXOffset());
|
||||
ASSERT_EQ(Y_OFFSET, event->getYOffset());
|
||||
ASSERT_EQ(2.0f, event->getXPrecision());
|
||||
ASSERT_EQ(2.1f, event->getYPrecision());
|
||||
ASSERT_EQ(ARBITRARY_DOWN_TIME, event->getDownTime());
|
||||
|
||||
ASSERT_EQ(2U, event->getPointerCount());
|
||||
ASSERT_EQ(1, event->getPointerId(0));
|
||||
ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, event->getToolType(0));
|
||||
ASSERT_EQ(2, event->getPointerId(1));
|
||||
ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, event->getToolType(1));
|
||||
|
||||
ASSERT_EQ(2U, event->getHistorySize());
|
||||
|
||||
// Check data.
|
||||
ASSERT_EQ(ARBITRARY_EVENT_TIME, event->getHistoricalEventTime(0));
|
||||
ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1));
|
||||
ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime());
|
||||
|
||||
ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
ASSERT_EQ(211, event->getRawPointerCoords(0)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
ASSERT_EQ(221, event->getRawPointerCoords(1)->
|
||||
getAxisValue(AMOTION_EVENT_AXIS_Y));
|
||||
|
||||
ASSERT_EQ(11, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
|
||||
ASSERT_EQ(21, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
|
||||
ASSERT_EQ(111, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
|
||||
ASSERT_EQ(121, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
|
||||
ASSERT_EQ(211, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
|
||||
ASSERT_EQ(221, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
|
||||
|
||||
ASSERT_EQ(10, event->getHistoricalRawX(0, 0));
|
||||
ASSERT_EQ(20, event->getHistoricalRawX(1, 0));
|
||||
ASSERT_EQ(110, event->getHistoricalRawX(0, 1));
|
||||
ASSERT_EQ(120, event->getHistoricalRawX(1, 1));
|
||||
ASSERT_EQ(210, event->getRawX(0));
|
||||
ASSERT_EQ(220, event->getRawX(1));
|
||||
|
||||
ASSERT_EQ(11, event->getHistoricalRawY(0, 0));
|
||||
ASSERT_EQ(21, event->getHistoricalRawY(1, 0));
|
||||
ASSERT_EQ(111, event->getHistoricalRawY(0, 1));
|
||||
ASSERT_EQ(121, event->getHistoricalRawY(1, 1));
|
||||
ASSERT_EQ(211, event->getRawY(0));
|
||||
ASSERT_EQ(221, event->getRawY(1));
|
||||
|
||||
ASSERT_EQ(X_OFFSET + 10, event->getHistoricalX(0, 0));
|
||||
ASSERT_EQ(X_OFFSET + 20, event->getHistoricalX(1, 0));
|
||||
ASSERT_EQ(X_OFFSET + 110, event->getHistoricalX(0, 1));
|
||||
ASSERT_EQ(X_OFFSET + 120, event->getHistoricalX(1, 1));
|
||||
ASSERT_EQ(X_OFFSET + 210, event->getX(0));
|
||||
ASSERT_EQ(X_OFFSET + 220, event->getX(1));
|
||||
|
||||
ASSERT_EQ(Y_OFFSET + 11, event->getHistoricalY(0, 0));
|
||||
ASSERT_EQ(Y_OFFSET + 21, event->getHistoricalY(1, 0));
|
||||
ASSERT_EQ(Y_OFFSET + 111, event->getHistoricalY(0, 1));
|
||||
ASSERT_EQ(Y_OFFSET + 121, event->getHistoricalY(1, 1));
|
||||
ASSERT_EQ(Y_OFFSET + 211, event->getY(0));
|
||||
ASSERT_EQ(Y_OFFSET + 221, event->getY(1));
|
||||
|
||||
ASSERT_EQ(12, event->getHistoricalPressure(0, 0));
|
||||
ASSERT_EQ(22, event->getHistoricalPressure(1, 0));
|
||||
ASSERT_EQ(112, event->getHistoricalPressure(0, 1));
|
||||
ASSERT_EQ(122, event->getHistoricalPressure(1, 1));
|
||||
ASSERT_EQ(212, event->getPressure(0));
|
||||
ASSERT_EQ(222, event->getPressure(1));
|
||||
|
||||
ASSERT_EQ(13, event->getHistoricalSize(0, 0));
|
||||
ASSERT_EQ(23, event->getHistoricalSize(1, 0));
|
||||
ASSERT_EQ(113, event->getHistoricalSize(0, 1));
|
||||
ASSERT_EQ(123, event->getHistoricalSize(1, 1));
|
||||
ASSERT_EQ(213, event->getSize(0));
|
||||
ASSERT_EQ(223, event->getSize(1));
|
||||
|
||||
ASSERT_EQ(14, event->getHistoricalTouchMajor(0, 0));
|
||||
ASSERT_EQ(24, event->getHistoricalTouchMajor(1, 0));
|
||||
ASSERT_EQ(114, event->getHistoricalTouchMajor(0, 1));
|
||||
ASSERT_EQ(124, event->getHistoricalTouchMajor(1, 1));
|
||||
ASSERT_EQ(214, event->getTouchMajor(0));
|
||||
ASSERT_EQ(224, event->getTouchMajor(1));
|
||||
|
||||
ASSERT_EQ(15, event->getHistoricalTouchMinor(0, 0));
|
||||
ASSERT_EQ(25, event->getHistoricalTouchMinor(1, 0));
|
||||
ASSERT_EQ(115, event->getHistoricalTouchMinor(0, 1));
|
||||
ASSERT_EQ(125, event->getHistoricalTouchMinor(1, 1));
|
||||
ASSERT_EQ(215, event->getTouchMinor(0));
|
||||
ASSERT_EQ(225, event->getTouchMinor(1));
|
||||
|
||||
ASSERT_EQ(16, event->getHistoricalToolMajor(0, 0));
|
||||
ASSERT_EQ(26, event->getHistoricalToolMajor(1, 0));
|
||||
ASSERT_EQ(116, event->getHistoricalToolMajor(0, 1));
|
||||
ASSERT_EQ(126, event->getHistoricalToolMajor(1, 1));
|
||||
ASSERT_EQ(216, event->getToolMajor(0));
|
||||
ASSERT_EQ(226, event->getToolMajor(1));
|
||||
|
||||
ASSERT_EQ(17, event->getHistoricalToolMinor(0, 0));
|
||||
ASSERT_EQ(27, event->getHistoricalToolMinor(1, 0));
|
||||
ASSERT_EQ(117, event->getHistoricalToolMinor(0, 1));
|
||||
ASSERT_EQ(127, event->getHistoricalToolMinor(1, 1));
|
||||
ASSERT_EQ(217, event->getToolMinor(0));
|
||||
ASSERT_EQ(227, event->getToolMinor(1));
|
||||
|
||||
ASSERT_EQ(18, event->getHistoricalOrientation(0, 0));
|
||||
ASSERT_EQ(28, event->getHistoricalOrientation(1, 0));
|
||||
ASSERT_EQ(118, event->getHistoricalOrientation(0, 1));
|
||||
ASSERT_EQ(128, event->getHistoricalOrientation(1, 1));
|
||||
ASSERT_EQ(218, event->getOrientation(0));
|
||||
ASSERT_EQ(228, event->getOrientation(1));
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, Properties) {
|
||||
MotionEvent event;
|
||||
|
||||
// Initialize, add samples and check properties.
|
||||
initializeEventWithHistory(&event);
|
||||
ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event));
|
||||
|
||||
// Set source.
|
||||
event.setSource(AINPUT_SOURCE_JOYSTICK);
|
||||
ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
|
||||
|
||||
// Set action.
|
||||
event.setAction(AMOTION_EVENT_ACTION_CANCEL);
|
||||
ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, event.getAction());
|
||||
|
||||
// Set meta state.
|
||||
event.setMetaState(AMETA_CTRL_ON);
|
||||
ASSERT_EQ(AMETA_CTRL_ON, event.getMetaState());
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, CopyFrom_KeepHistory) {
|
||||
MotionEvent event;
|
||||
initializeEventWithHistory(&event);
|
||||
|
||||
MotionEvent copy;
|
||||
copy.copyFrom(&event, true /*keepHistory*/);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event));
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, CopyFrom_DoNotKeepHistory) {
|
||||
MotionEvent event;
|
||||
initializeEventWithHistory(&event);
|
||||
|
||||
MotionEvent copy;
|
||||
copy.copyFrom(&event, false /*keepHistory*/);
|
||||
|
||||
ASSERT_EQ(event.getPointerCount(), copy.getPointerCount());
|
||||
ASSERT_EQ(0U, copy.getHistorySize());
|
||||
|
||||
ASSERT_EQ(event.getPointerId(0), copy.getPointerId(0));
|
||||
ASSERT_EQ(event.getPointerId(1), copy.getPointerId(1));
|
||||
|
||||
ASSERT_EQ(event.getEventTime(), copy.getEventTime());
|
||||
|
||||
ASSERT_EQ(event.getX(0), copy.getX(0));
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, OffsetLocation) {
|
||||
MotionEvent event;
|
||||
initializeEventWithHistory(&event);
|
||||
|
||||
event.offsetLocation(5.0f, -2.0f);
|
||||
|
||||
ASSERT_EQ(X_OFFSET + 5.0f, event.getXOffset());
|
||||
ASSERT_EQ(Y_OFFSET - 2.0f, event.getYOffset());
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, Scale) {
|
||||
MotionEvent event;
|
||||
initializeEventWithHistory(&event);
|
||||
|
||||
event.scale(2.0f);
|
||||
|
||||
ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
|
||||
ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
|
||||
|
||||
ASSERT_EQ(210 * 2, event.getRawX(0));
|
||||
ASSERT_EQ(211 * 2, event.getRawY(0));
|
||||
ASSERT_EQ((X_OFFSET + 210) * 2, event.getX(0));
|
||||
ASSERT_EQ((Y_OFFSET + 211) * 2, event.getY(0));
|
||||
ASSERT_EQ(212, event.getPressure(0));
|
||||
ASSERT_EQ(213, event.getSize(0));
|
||||
ASSERT_EQ(214 * 2, event.getTouchMajor(0));
|
||||
ASSERT_EQ(215 * 2, event.getTouchMinor(0));
|
||||
ASSERT_EQ(216 * 2, event.getToolMajor(0));
|
||||
ASSERT_EQ(217 * 2, event.getToolMinor(0));
|
||||
ASSERT_EQ(218, event.getOrientation(0));
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, Parcel) {
|
||||
Parcel parcel;
|
||||
|
||||
MotionEvent inEvent;
|
||||
initializeEventWithHistory(&inEvent);
|
||||
MotionEvent outEvent;
|
||||
|
||||
// Round trip.
|
||||
inEvent.writeToParcel(&parcel);
|
||||
parcel.setDataPosition(0);
|
||||
outEvent.readFromParcel(&parcel);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&outEvent));
|
||||
}
|
||||
|
||||
TEST_F(MotionEventTest, Transform) {
|
||||
// Generate some points on a circle.
|
||||
// Each point 'i' is a point on a circle of radius ROTATION centered at (3,2) at an angle
|
||||
// of ARC * i degrees clockwise relative to the Y axis.
|
||||
// The geometrical representation is irrelevant to the test, it's just easy to generate
|
||||
// and check rotation. We set the orientation to the same angle.
|
||||
// Coordinate system: down is increasing Y, right is increasing X.
|
||||
const float PI_180 = float(M_PI / 180);
|
||||
const float RADIUS = 10;
|
||||
const float ARC = 36;
|
||||
const float ROTATION = ARC * 2;
|
||||
|
||||
const size_t pointerCount = 11;
|
||||
PointerProperties pointerProperties[pointerCount];
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
float angle = float(i * ARC * PI_180);
|
||||
pointerProperties[i].clear();
|
||||
pointerProperties[i].id = i;
|
||||
pointerCoords[i].clear();
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, sinf(angle) * RADIUS + 3);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, -cosf(angle) * RADIUS + 2);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, angle);
|
||||
}
|
||||
MotionEvent event;
|
||||
event.initialize(0, 0, AMOTION_EVENT_ACTION_MOVE, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, pointerCount, pointerProperties, pointerCoords);
|
||||
float originalRawX = 0 + 3;
|
||||
float originalRawY = -RADIUS + 2;
|
||||
|
||||
// Check original raw X and Y assumption.
|
||||
ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001);
|
||||
ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001);
|
||||
|
||||
// Now translate the motion event so the circle's origin is at (0,0).
|
||||
event.offsetLocation(-3, -2);
|
||||
|
||||
// Offsetting the location should preserve the raw X and Y of the first point.
|
||||
ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001);
|
||||
ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001);
|
||||
|
||||
// Apply a rotation about the origin by ROTATION degrees clockwise.
|
||||
SkMatrix matrix;
|
||||
matrix.setRotate(ROTATION);
|
||||
event.transform(&matrix);
|
||||
|
||||
// Check the points.
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
float angle = float((i * ARC + ROTATION) * PI_180);
|
||||
ASSERT_NEAR(sinf(angle) * RADIUS, event.getX(i), 0.001);
|
||||
ASSERT_NEAR(-cosf(angle) * RADIUS, event.getY(i), 0.001);
|
||||
ASSERT_NEAR(tanf(angle), tanf(event.getOrientation(i)), 0.1);
|
||||
}
|
||||
|
||||
// Applying the transformation should preserve the raw X and Y of the first point.
|
||||
ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001);
|
||||
ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001);
|
||||
}
|
||||
|
||||
} // namespace android
|
288
libs/input/tests/InputPublisherAndConsumer_test.cpp
Normal file
288
libs/input/tests/InputPublisherAndConsumer_test.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#include "TestHelpers.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <cutils/ashmem.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <input/InputTransport.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/StopWatch.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class InputPublisherAndConsumerTest : public testing::Test {
|
||||
protected:
|
||||
sp<InputChannel> serverChannel, clientChannel;
|
||||
InputPublisher* mPublisher;
|
||||
InputConsumer* mConsumer;
|
||||
PreallocatedInputEventFactory mEventFactory;
|
||||
|
||||
virtual void SetUp() {
|
||||
status_t result = InputChannel::openInputChannelPair(String8("channel name"),
|
||||
serverChannel, clientChannel);
|
||||
|
||||
mPublisher = new InputPublisher(serverChannel);
|
||||
mConsumer = new InputConsumer(clientChannel);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
if (mPublisher) {
|
||||
delete mPublisher;
|
||||
mPublisher = NULL;
|
||||
}
|
||||
|
||||
if (mConsumer) {
|
||||
delete mConsumer;
|
||||
mConsumer = NULL;
|
||||
}
|
||||
|
||||
serverChannel.clear();
|
||||
clientChannel.clear();
|
||||
}
|
||||
|
||||
void PublishAndConsumeKeyEvent();
|
||||
void PublishAndConsumeMotionEvent();
|
||||
};
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
|
||||
EXPECT_EQ(serverChannel.get(), mPublisher->getChannel().get());
|
||||
EXPECT_EQ(clientChannel.get(), mConsumer->getChannel().get());
|
||||
}
|
||||
|
||||
void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
|
||||
status_t status;
|
||||
|
||||
const uint32_t seq = 15;
|
||||
const int32_t deviceId = 1;
|
||||
const int32_t source = AINPUT_SOURCE_KEYBOARD;
|
||||
const int32_t action = AKEY_EVENT_ACTION_DOWN;
|
||||
const int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
|
||||
const int32_t keyCode = AKEYCODE_ENTER;
|
||||
const int32_t scanCode = 13;
|
||||
const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
|
||||
const int32_t repeatCount = 1;
|
||||
const nsecs_t downTime = 3;
|
||||
const nsecs_t eventTime = 4;
|
||||
|
||||
status = mPublisher->publishKeyEvent(seq, deviceId, source, action, flags,
|
||||
keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "publisher publishKeyEvent should return OK";
|
||||
|
||||
uint32_t consumeSeq;
|
||||
InputEvent* event;
|
||||
status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "consumer consume should return OK";
|
||||
|
||||
ASSERT_TRUE(event != NULL)
|
||||
<< "consumer should have returned non-NULL event";
|
||||
ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event->getType())
|
||||
<< "consumer should have returned a key event";
|
||||
|
||||
KeyEvent* keyEvent = static_cast<KeyEvent*>(event);
|
||||
EXPECT_EQ(seq, consumeSeq);
|
||||
EXPECT_EQ(deviceId, keyEvent->getDeviceId());
|
||||
EXPECT_EQ(source, keyEvent->getSource());
|
||||
EXPECT_EQ(action, keyEvent->getAction());
|
||||
EXPECT_EQ(flags, keyEvent->getFlags());
|
||||
EXPECT_EQ(keyCode, keyEvent->getKeyCode());
|
||||
EXPECT_EQ(scanCode, keyEvent->getScanCode());
|
||||
EXPECT_EQ(metaState, keyEvent->getMetaState());
|
||||
EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
|
||||
EXPECT_EQ(downTime, keyEvent->getDownTime());
|
||||
EXPECT_EQ(eventTime, keyEvent->getEventTime());
|
||||
|
||||
status = mConsumer->sendFinishedSignal(seq, true);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "consumer sendFinishedSignal should return OK";
|
||||
|
||||
uint32_t finishedSeq = 0;
|
||||
bool handled = false;
|
||||
status = mPublisher->receiveFinishedSignal(&finishedSeq, &handled);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "publisher receiveFinishedSignal should return OK";
|
||||
ASSERT_EQ(seq, finishedSeq)
|
||||
<< "publisher receiveFinishedSignal should have returned the original sequence number";
|
||||
ASSERT_TRUE(handled)
|
||||
<< "publisher receiveFinishedSignal should have set handled to consumer's reply";
|
||||
}
|
||||
|
||||
void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
|
||||
status_t status;
|
||||
|
||||
const uint32_t seq = 15;
|
||||
const int32_t deviceId = 1;
|
||||
const int32_t source = AINPUT_SOURCE_TOUCHSCREEN;
|
||||
const int32_t action = AMOTION_EVENT_ACTION_MOVE;
|
||||
const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
|
||||
const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
|
||||
const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
|
||||
const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
|
||||
const float xOffset = -10;
|
||||
const float yOffset = -20;
|
||||
const float xPrecision = 0.25;
|
||||
const float yPrecision = 0.5;
|
||||
const nsecs_t downTime = 3;
|
||||
const size_t pointerCount = 3;
|
||||
const nsecs_t eventTime = 4;
|
||||
PointerProperties pointerProperties[pointerCount];
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
pointerProperties[i].clear();
|
||||
pointerProperties[i].id = (i + 2) % pointerCount;
|
||||
pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
|
||||
|
||||
pointerCoords[i].clear();
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
|
||||
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
|
||||
}
|
||||
|
||||
status = mPublisher->publishMotionEvent(seq, deviceId, source, action, flags, edgeFlags,
|
||||
metaState, buttonState, xOffset, yOffset, xPrecision, yPrecision,
|
||||
downTime, eventTime, pointerCount,
|
||||
pointerProperties, pointerCoords);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "publisher publishMotionEvent should return OK";
|
||||
|
||||
uint32_t consumeSeq;
|
||||
InputEvent* event;
|
||||
status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "consumer consume should return OK";
|
||||
|
||||
ASSERT_TRUE(event != NULL)
|
||||
<< "consumer should have returned non-NULL event";
|
||||
ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType())
|
||||
<< "consumer should have returned a motion event";
|
||||
|
||||
MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
|
||||
EXPECT_EQ(seq, consumeSeq);
|
||||
EXPECT_EQ(deviceId, motionEvent->getDeviceId());
|
||||
EXPECT_EQ(source, motionEvent->getSource());
|
||||
EXPECT_EQ(action, motionEvent->getAction());
|
||||
EXPECT_EQ(flags, motionEvent->getFlags());
|
||||
EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
|
||||
EXPECT_EQ(metaState, motionEvent->getMetaState());
|
||||
EXPECT_EQ(buttonState, motionEvent->getButtonState());
|
||||
EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
|
||||
EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
|
||||
EXPECT_EQ(downTime, motionEvent->getDownTime());
|
||||
EXPECT_EQ(eventTime, motionEvent->getEventTime());
|
||||
EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
|
||||
EXPECT_EQ(0U, motionEvent->getHistorySize());
|
||||
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
SCOPED_TRACE(i);
|
||||
EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i));
|
||||
EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
|
||||
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
|
||||
motionEvent->getRawX(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
|
||||
motionEvent->getRawY(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X) + xOffset,
|
||||
motionEvent->getX(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y) + yOffset,
|
||||
motionEvent->getY(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
|
||||
motionEvent->getPressure(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
|
||||
motionEvent->getSize(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
|
||||
motionEvent->getTouchMajor(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
|
||||
motionEvent->getTouchMinor(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
|
||||
motionEvent->getToolMajor(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
|
||||
motionEvent->getToolMinor(i));
|
||||
EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
|
||||
motionEvent->getOrientation(i));
|
||||
}
|
||||
|
||||
status = mConsumer->sendFinishedSignal(seq, false);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "consumer sendFinishedSignal should return OK";
|
||||
|
||||
uint32_t finishedSeq = 0;
|
||||
bool handled = true;
|
||||
status = mPublisher->receiveFinishedSignal(&finishedSeq, &handled);
|
||||
ASSERT_EQ(OK, status)
|
||||
<< "publisher receiveFinishedSignal should return OK";
|
||||
ASSERT_EQ(seq, finishedSeq)
|
||||
<< "publisher receiveFinishedSignal should have returned the original sequence number";
|
||||
ASSERT_FALSE(handled)
|
||||
<< "publisher receiveFinishedSignal should have set handled to consumer's reply";
|
||||
}
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
|
||||
}
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
|
||||
}
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
|
||||
status_t status;
|
||||
const size_t pointerCount = 0;
|
||||
PointerProperties pointerProperties[pointerCount];
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
|
||||
status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
pointerCount, pointerProperties, pointerCoords);
|
||||
ASSERT_EQ(BAD_VALUE, status)
|
||||
<< "publisher publishMotionEvent should return BAD_VALUE";
|
||||
}
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
|
||||
status_t status;
|
||||
const size_t pointerCount = MAX_POINTERS + 1;
|
||||
PointerProperties pointerProperties[pointerCount];
|
||||
PointerCoords pointerCoords[pointerCount];
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
pointerProperties[i].clear();
|
||||
pointerCoords[i].clear();
|
||||
}
|
||||
|
||||
status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
pointerCount, pointerProperties, pointerCoords);
|
||||
ASSERT_EQ(BAD_VALUE, status)
|
||||
<< "publisher publishMotionEvent should return BAD_VALUE";
|
||||
}
|
||||
|
||||
TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
|
||||
ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
|
||||
}
|
||||
|
||||
} // namespace android
|
81
libs/input/tests/TestHelpers.h
Normal file
81
libs/input/tests/TestHelpers.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#ifndef TESTHELPERS_H
|
||||
#define TESTHELPERS_H
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utils/threads.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class Pipe {
|
||||
public:
|
||||
int sendFd;
|
||||
int receiveFd;
|
||||
|
||||
Pipe() {
|
||||
int fds[2];
|
||||
::pipe(fds);
|
||||
|
||||
receiveFd = fds[0];
|
||||
sendFd = fds[1];
|
||||
}
|
||||
|
||||
~Pipe() {
|
||||
if (sendFd != -1) {
|
||||
::close(sendFd);
|
||||
}
|
||||
|
||||
if (receiveFd != -1) {
|
||||
::close(receiveFd);
|
||||
}
|
||||
}
|
||||
|
||||
status_t writeSignal() {
|
||||
ssize_t nWritten = ::write(sendFd, "*", 1);
|
||||
return nWritten == 1 ? 0 : -errno;
|
||||
}
|
||||
|
||||
status_t readSignal() {
|
||||
char buf[1];
|
||||
ssize_t nRead = ::read(receiveFd, buf, 1);
|
||||
return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno;
|
||||
}
|
||||
};
|
||||
|
||||
class DelayedTask : public Thread {
|
||||
int mDelayMillis;
|
||||
|
||||
public:
|
||||
DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { }
|
||||
|
||||
protected:
|
||||
virtual ~DelayedTask() { }
|
||||
|
||||
virtual void doTask() = 0;
|
||||
|
||||
virtual bool threadLoop() {
|
||||
usleep(mDelayMillis * 1000);
|
||||
doTask();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // TESTHELPERS_H
|
Loading…
Reference in New Issue
Block a user