blob: decbea4c3e1d5a74e03a8b41238eea910c6c011b [file] [log] [blame]
/*
* Copyright (C) 2019 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 "../Macros.h"
#include "TouchInputMapper.h"
#include "CursorButtonAccumulator.h"
#include "CursorScrollAccumulator.h"
#include "TouchButtonAccumulator.h"
#include "TouchCursorInputMapperCommon.h"
namespace android {
// --- Constants ---
// Maximum amount of latency to add to touch events while waiting for data from an
// external stylus.
static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72);
// Maximum amount of time to wait on touch data before pushing out new pressure data.
static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20);
// Artificial latency on synthetic events created from stylus data without corresponding touch
// data.
static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10);
// --- Static Definitions ---
template <typename T>
inline static void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
static float calculateCommonVector(float a, float b) {
if (a > 0 && b > 0) {
return a < b ? a : b;
} else if (a < 0 && b < 0) {
return a > b ? a : b;
} else {
return 0;
}
}
inline static float distance(float x1, float y1, float x2, float y2) {
return hypotf(x1 - x2, y1 - y2);
}
inline static int32_t signExtendNybble(int32_t value) {
return value >= 8 ? value - 16 : value;
}
// --- RawPointerAxes ---
RawPointerAxes::RawPointerAxes() {
clear();
}
void RawPointerAxes::clear() {
x.clear();
y.clear();
pressure.clear();
touchMajor.clear();
touchMinor.clear();
toolMajor.clear();
toolMinor.clear();
orientation.clear();
distance.clear();
tiltX.clear();
tiltY.clear();
trackingId.clear();
slot.clear();
}
// --- RawPointerData ---
RawPointerData::RawPointerData() {
clear();
}
void RawPointerData::clear() {
pointerCount = 0;
clearIdBits();
}
void RawPointerData::copyFrom(const RawPointerData& other) {
pointerCount = other.pointerCount;
hoveringIdBits = other.hoveringIdBits;
touchingIdBits = other.touchingIdBits;
for (uint32_t i = 0; i < pointerCount; i++) {
pointers[i] = other.pointers[i];
int id = pointers[i].id;
idToIndex[id] = other.idToIndex[id];
}
}
void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
float x = 0, y = 0;
uint32_t count = touchingIdBits.count();
if (count) {
for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const Pointer& pointer = pointerForId(id);
x += pointer.x;
y += pointer.y;
}
x /= count;
y /= count;
}
*outX = x;
*outY = y;
}
// --- CookedPointerData ---
CookedPointerData::CookedPointerData() {
clear();
}
void CookedPointerData::clear() {
pointerCount = 0;
hoveringIdBits.clear();
touchingIdBits.clear();
}
void CookedPointerData::copyFrom(const CookedPointerData& other) {
pointerCount = other.pointerCount;
hoveringIdBits = other.hoveringIdBits;
touchingIdBits = other.touchingIdBits;
for (uint32_t i = 0; i < pointerCount; i++) {
pointerProperties[i].copyFrom(other.pointerProperties[i]);
pointerCoords[i].copyFrom(other.pointerCoords[i]);
int id = pointerProperties[i].id;
idToIndex[id] = other.idToIndex[id];
}
}
// --- TouchInputMapper ---
TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext)
: InputMapper(deviceContext),
mSource(0),
mDeviceMode(DEVICE_MODE_DISABLED),
mRawSurfaceWidth(-1),
mRawSurfaceHeight(-1),
mSurfaceLeft(0),
mSurfaceTop(0),
mPhysicalWidth(-1),
mPhysicalHeight(-1),
mPhysicalLeft(0),
mPhysicalTop(0),
mSurfaceOrientation(DISPLAY_ORIENTATION_0) {}
TouchInputMapper::~TouchInputMapper() {}
uint32_t TouchInputMapper::getSources() {
return mSource;
}
void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
InputMapper::populateDeviceInfo(info);
if (mDeviceMode != DEVICE_MODE_DISABLED) {
info->addMotionRange(mOrientedRanges.x);
info->addMotionRange(mOrientedRanges.y);
info->addMotionRange(mOrientedRanges.pressure);
if (mOrientedRanges.haveSize) {
info->addMotionRange(mOrientedRanges.size);
}
if (mOrientedRanges.haveTouchSize) {
info->addMotionRange(mOrientedRanges.touchMajor);
info->addMotionRange(mOrientedRanges.touchMinor);
}
if (mOrientedRanges.haveToolSize) {
info->addMotionRange(mOrientedRanges.toolMajor);
info->addMotionRange(mOrientedRanges.toolMinor);
}
if (mOrientedRanges.haveOrientation) {
info->addMotionRange(mOrientedRanges.orientation);
}
if (mOrientedRanges.haveDistance) {
info->addMotionRange(mOrientedRanges.distance);
}
if (mOrientedRanges.haveTilt) {
info->addMotionRange(mOrientedRanges.tilt);
}
if (mCursorScrollAccumulator.haveRelativeVWheel()) {
info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
0.0f);
}
if (mCursorScrollAccumulator.haveRelativeHWheel()) {
info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
0.0f);
}
if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
x.fuzz, x.resolution);
info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
y.fuzz, y.resolution);
info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
x.fuzz, x.resolution);
info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
y.fuzz, y.resolution);
}
info->setButtonUnderPad(mParameters.hasButtonUnderPad);
}
}
void TouchInputMapper::dump(std::string& dump) {
dump += StringPrintf(INDENT2 "Touch Input Mapper (mode - %s):\n", modeToString(mDeviceMode));
dumpParameters(dump);
dumpVirtualKeys(dump);
dumpRawPointerAxes(dump);
dumpCalibration(dump);
dumpAffineTransformation(dump);
dumpSurface(dump);
dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n");
dump += StringPrintf(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
dump += StringPrintf(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale);
dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale);
dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
dump += StringPrintf(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale);
dump += StringPrintf(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale);
dump += StringPrintf(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt));
dump += StringPrintf(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter);
dump += StringPrintf(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale);
dump += StringPrintf(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter);
dump += StringPrintf(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale);
dump += StringPrintf(INDENT3 "Last Raw Button State: 0x%08x\n", mLastRawState.buttonState);
dump += StringPrintf(INDENT3 "Last Raw Touch: pointerCount=%d\n",
mLastRawState.rawPointerData.pointerCount);
for (uint32_t i = 0; i < mLastRawState.rawPointerData.pointerCount; i++) {
const RawPointerData::Pointer& pointer = mLastRawState.rawPointerData.pointers[i];
dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
"touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
"orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
"toolType=%d, isHovering=%s\n",
i, pointer.id, pointer.x, pointer.y, pointer.pressure,
pointer.touchMajor, pointer.touchMinor, pointer.toolMajor,
pointer.toolMinor, pointer.orientation, pointer.tiltX, pointer.tiltY,
pointer.distance, pointer.toolType, toString(pointer.isHovering));
}
dump += StringPrintf(INDENT3 "Last Cooked Button State: 0x%08x\n",
mLastCookedState.buttonState);
dump += StringPrintf(INDENT3 "Last Cooked Touch: pointerCount=%d\n",
mLastCookedState.cookedPointerData.pointerCount);
for (uint32_t i = 0; i < mLastCookedState.cookedPointerData.pointerCount; i++) {
const PointerProperties& pointerProperties =
mLastCookedState.cookedPointerData.pointerProperties[i];
const PointerCoords& pointerCoords = mLastCookedState.cookedPointerData.pointerCoords[i];
dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, "
"touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, "
"toolMinor=%0.3f, "
"orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
"toolType=%d, isHovering=%s\n",
i, pointerProperties.id, pointerCoords.getX(), pointerCoords.getY(),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
pointerProperties.toolType,
toString(mLastCookedState.cookedPointerData.isHovering(i)));
}
dump += INDENT3 "Stylus Fusion:\n";
dump += StringPrintf(INDENT4 "ExternalStylusConnected: %s\n",
toString(mExternalStylusConnected));
dump += StringPrintf(INDENT4 "External Stylus ID: %" PRId64 "\n", mExternalStylusId);
dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n",
mExternalStylusFusionTimeout);
dump += INDENT3 "External Stylus State:\n";
dumpStylusState(dump, mExternalStylusState);
if (mDeviceMode == DEVICE_MODE_POINTER) {
dump += StringPrintf(INDENT3 "Pointer Gesture Detector:\n");
dump += StringPrintf(INDENT4 "XMovementScale: %0.3f\n", mPointerXMovementScale);
dump += StringPrintf(INDENT4 "YMovementScale: %0.3f\n", mPointerYMovementScale);
dump += StringPrintf(INDENT4 "XZoomScale: %0.3f\n", mPointerXZoomScale);
dump += StringPrintf(INDENT4 "YZoomScale: %0.3f\n", mPointerYZoomScale);
dump += StringPrintf(INDENT4 "MaxSwipeWidth: %f\n", mPointerGestureMaxSwipeWidth);
}
}
const char* TouchInputMapper::modeToString(DeviceMode deviceMode) {
switch (deviceMode) {
case DEVICE_MODE_DISABLED:
return "disabled";
case DEVICE_MODE_DIRECT:
return "direct";
case DEVICE_MODE_UNSCALED:
return "unscaled";
case DEVICE_MODE_NAVIGATION:
return "navigation";
case DEVICE_MODE_POINTER:
return "pointer";
}
return "unknown";
}
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
uint32_t changes) {
InputMapper::configure(when, config, changes);
mConfig = *config;
if (!changes) { // first time only
// Configure basic parameters.
configureParameters();
// Configure common accumulators.
mCursorScrollAccumulator.configure(getDeviceContext());
mTouchButtonAccumulator.configure(getDeviceContext());
// Configure absolute axis information.
configureRawPointerAxes();
// Prepare input device calibration.
parseCalibration();
resolveCalibration();
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) {
// Update location calibration to reflect current settings
updateAffineTransformation();
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
// Update pointer speed.
mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
}
bool resetNeeded = false;
if (!changes ||
(changes &
(InputReaderConfiguration::CHANGE_DISPLAY_INFO |
InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
// Configure device sources, surface dimensions, orientation and
// scaling factors.
configureSurface(when, &resetNeeded);
}
if (changes && resetNeeded) {
// Send reset, unless this is the first time the device has been configured,
// in which case the reader will call reset itself after all mappers are ready.
NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
getListener()->notifyDeviceReset(&args);
}
}
void TouchInputMapper::resolveExternalStylusPresence() {
std::vector<InputDeviceInfo> devices;
getContext()->getExternalStylusDevices(devices);
mExternalStylusConnected = !devices.empty();
if (!mExternalStylusConnected) {
resetExternalStylus();
}
}
void TouchInputMapper::configureParameters() {
// Use the pointer presentation mode for devices that do not support distinct
// multitouch. The spot-based presentation relies on being able to accurately
// locate two or more fingers on the touch pad.
mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT)
? Parameters::GESTURE_MODE_SINGLE_TOUCH
: Parameters::GESTURE_MODE_MULTI_TOUCH;
String8 gestureModeString;
if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.gestureMode"),
gestureModeString)) {
if (gestureModeString == "single-touch") {
mParameters.gestureMode = Parameters::GESTURE_MODE_SINGLE_TOUCH;
} else if (gestureModeString == "multi-touch") {
mParameters.gestureMode = Parameters::GESTURE_MODE_MULTI_TOUCH;
} else if (gestureModeString != "default") {
ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
}
}
if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
// The device is a touch screen.
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
} else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
// The device is a pointing device like a track pad.
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
} else if (getDeviceContext().hasRelativeAxis(REL_X) ||
getDeviceContext().hasRelativeAxis(REL_Y)) {
// The device is a cursor device with a touch pad attached.
// By default don't use the touch pad to move the pointer.
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
} else {
// The device is a touch pad of unknown purpose.
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
}
mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
String8 deviceTypeString;
if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.deviceType"),
deviceTypeString)) {
if (deviceTypeString == "touchScreen") {
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
} else if (deviceTypeString == "touchPad") {
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
} else if (deviceTypeString == "touchNavigation") {
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
} else if (deviceTypeString == "pointer") {
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
} else if (deviceTypeString != "default") {
ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
}
}
mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientationAware"),
mParameters.orientationAware);
mParameters.hasAssociatedDisplay = false;
mParameters.associatedDisplayIsExternal = false;
if (mParameters.orientationAware ||
mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN ||
mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
mParameters.hasAssociatedDisplay = true;
if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) {
mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
String8 uniqueDisplayId;
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.displayId"),
uniqueDisplayId);
mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
}
}
if (getDeviceContext().getAssociatedDisplayPort()) {
mParameters.hasAssociatedDisplay = true;
}
// Initial downs on external touch devices should wake the device.
// Normally we don't do this for internal touch screens to prevent them from waking
// up in your pocket but you can enable it using the input device configuration.
mParameters.wake = getDeviceContext().isExternal();
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake);
}
void TouchInputMapper::dumpParameters(std::string& dump) {
dump += INDENT3 "Parameters:\n";
switch (mParameters.gestureMode) {
case Parameters::GESTURE_MODE_SINGLE_TOUCH:
dump += INDENT4 "GestureMode: single-touch\n";
break;
case Parameters::GESTURE_MODE_MULTI_TOUCH:
dump += INDENT4 "GestureMode: multi-touch\n";
break;
default:
assert(false);
}
switch (mParameters.deviceType) {
case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
dump += INDENT4 "DeviceType: touchScreen\n";
break;
case Parameters::DEVICE_TYPE_TOUCH_PAD:
dump += INDENT4 "DeviceType: touchPad\n";
break;
case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
dump += INDENT4 "DeviceType: touchNavigation\n";
break;
case Parameters::DEVICE_TYPE_POINTER:
dump += INDENT4 "DeviceType: pointer\n";
break;
default:
ALOG_ASSERT(false);
}
dump += StringPrintf(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s, "
"displayId='%s'\n",
toString(mParameters.hasAssociatedDisplay),
toString(mParameters.associatedDisplayIsExternal),
mParameters.uniqueDisplayId.c_str());
dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
}
void TouchInputMapper::configureRawPointerAxes() {
mRawPointerAxes.clear();
}
void TouchInputMapper::dumpRawPointerAxes(std::string& dump) {
dump += INDENT3 "Raw Touch Axes:\n";
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId");
dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot");
}
bool TouchInputMapper::hasExternalStylus() const {
return mExternalStylusConnected;
}
/**
* Determine which DisplayViewport to use.
* 1. If display port is specified, return the matching viewport. If matching viewport not
* found, then return.
* 2. Always use the suggested viewport from WindowManagerService for pointers.
* 3. If a device has associated display, get the matching viewport by either unique id or by
* the display type (internal or external).
* 4. Otherwise, use a non-display viewport.
*/
std::optional<DisplayViewport> TouchInputMapper::findViewport() {
if (mParameters.hasAssociatedDisplay) {
const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
if (displayPort) {
// Find the viewport that contains the same port
return getDeviceContext().getAssociatedViewport();
}
if (mDeviceMode == DEVICE_MODE_POINTER) {
std::optional<DisplayViewport> viewport =
mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
if (viewport) {
return viewport;
} else {
ALOGW("Can't find designated display viewport with ID %" PRId32 " for pointers.",
mConfig.defaultPointerDisplayId);
}
}
// Check if uniqueDisplayId is specified in idc file.
if (!mParameters.uniqueDisplayId.empty()) {
return mConfig.getDisplayViewportByUniqueId(mParameters.uniqueDisplayId);
}
ViewportType viewportTypeToUse;
if (mParameters.associatedDisplayIsExternal) {
viewportTypeToUse = ViewportType::VIEWPORT_EXTERNAL;
} else {
viewportTypeToUse = ViewportType::VIEWPORT_INTERNAL;
}
std::optional<DisplayViewport> viewport =
mConfig.getDisplayViewportByType(viewportTypeToUse);
if (!viewport && viewportTypeToUse == ViewportType::VIEWPORT_EXTERNAL) {
ALOGW("Input device %s should be associated with external display, "
"fallback to internal one for the external viewport is not found.",
getDeviceName().c_str());
viewport = mConfig.getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
}
return viewport;
}
// No associated display, return a non-display viewport.
DisplayViewport newViewport;
// Raw width and height in the natural orientation.
int32_t rawWidth = mRawPointerAxes.getRawWidth();
int32_t rawHeight = mRawPointerAxes.getRawHeight();
newViewport.setNonDisplayViewport(rawWidth, rawHeight);
return std::make_optional(newViewport);
}
void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
int32_t oldDeviceMode = mDeviceMode;
resolveExternalStylusPresence();
// Determine device mode.
if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER &&
mConfig.pointerGesturesEnabled) {
mSource = AINPUT_SOURCE_MOUSE;
mDeviceMode = DEVICE_MODE_POINTER;
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
} else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN &&
mParameters.hasAssociatedDisplay) {
mSource = AINPUT_SOURCE_TOUCHSCREEN;
mDeviceMode = DEVICE_MODE_DIRECT;
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
if (hasExternalStylus()) {
mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
}
} else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
mDeviceMode = DEVICE_MODE_NAVIGATION;
} else {
mSource = AINPUT_SOURCE_TOUCHPAD;
mDeviceMode = DEVICE_MODE_UNSCALED;
}
// Ensure we have valid X and Y axes.
if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
ALOGW("Touch device '%s' did not report support for X or Y axis! "
"The device will be inoperable.",
getDeviceName().c_str());
mDeviceMode = DEVICE_MODE_DISABLED;
return;
}
// Get associated display dimensions.
std::optional<DisplayViewport> newViewport = findViewport();
if (!newViewport) {
ALOGI("Touch device '%s' could not query the properties of its associated "
"display. The device will be inoperable until the display size "
"becomes available.",
getDeviceName().c_str());
mDeviceMode = DEVICE_MODE_DISABLED;
return;
}
// Raw width and height in the natural orientation.
int32_t rawWidth = mRawPointerAxes.getRawWidth();
int32_t rawHeight = mRawPointerAxes.getRawHeight();
bool viewportChanged = mViewport != *newViewport;
if (viewportChanged) {
mViewport = *newViewport;
if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
// Convert rotated viewport to natural surface coordinates.
int32_t naturalLogicalWidth, naturalLogicalHeight;
int32_t naturalPhysicalWidth, naturalPhysicalHeight;
int32_t naturalPhysicalLeft, naturalPhysicalTop;
int32_t naturalDeviceWidth, naturalDeviceHeight;
switch (mViewport.orientation) {
case DISPLAY_ORIENTATION_90:
naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
naturalPhysicalTop = mViewport.physicalLeft;
naturalDeviceWidth = mViewport.deviceHeight;
naturalDeviceHeight = mViewport.deviceWidth;
break;
case DISPLAY_ORIENTATION_180:
naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
naturalDeviceWidth = mViewport.deviceWidth;
naturalDeviceHeight = mViewport.deviceHeight;
break;
case DISPLAY_ORIENTATION_270:
naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalLeft = mViewport.physicalTop;
naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
naturalDeviceWidth = mViewport.deviceHeight;
naturalDeviceHeight = mViewport.deviceWidth;
break;
case DISPLAY_ORIENTATION_0:
default:
naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalLeft = mViewport.physicalLeft;
naturalPhysicalTop = mViewport.physicalTop;
naturalDeviceWidth = mViewport.deviceWidth;
naturalDeviceHeight = mViewport.deviceHeight;
break;
}
if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) {
ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str());
naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight;
naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth;
}
mPhysicalWidth = naturalPhysicalWidth;
mPhysicalHeight = naturalPhysicalHeight;
mPhysicalLeft = naturalPhysicalLeft;
mPhysicalTop = naturalPhysicalTop;
mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
mSurfaceOrientation =
mParameters.orientationAware ? mViewport.orientation : DISPLAY_ORIENTATION_0;
} else {
mPhysicalWidth = rawWidth;
mPhysicalHeight = rawHeight;
mPhysicalLeft = 0;
mPhysicalTop = 0;
mRawSurfaceWidth = rawWidth;
mRawSurfaceHeight = rawHeight;
mSurfaceLeft = 0;
mSurfaceTop = 0;
mSurfaceOrientation = DISPLAY_ORIENTATION_0;
}
}
// If moving between pointer modes, need to reset some state.
bool deviceModeChanged = mDeviceMode != oldDeviceMode;
if (deviceModeChanged) {
mOrientedRanges.clear();
}
// Create pointer controller if needed.
if (mDeviceMode == DEVICE_MODE_POINTER ||
(mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
if (mPointerController == nullptr) {
mPointerController = getContext()->getPointerController(getDeviceId());
}
} else {
mPointerController.clear();
}
if (viewportChanged || deviceModeChanged) {
ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
"display id %d",
getDeviceId(), getDeviceName().c_str(), mRawSurfaceWidth, mRawSurfaceHeight,
mSurfaceOrientation, mDeviceMode, mViewport.displayId);
// Configure X and Y factors.
mXScale = float(mRawSurfaceWidth) / rawWidth;
mYScale = float(mRawSurfaceHeight) / rawHeight;
mXTranslate = -mSurfaceLeft;
mYTranslate = -mSurfaceTop;
mXPrecision = 1.0f / mXScale;
mYPrecision = 1.0f / mYScale;
mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
mOrientedRanges.x.source = mSource;
mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
mOrientedRanges.y.source = mSource;
configureVirtualKeys();
// Scale factor for terms that are not oriented in a particular axis.
// If the pixels are square then xScale == yScale otherwise we fake it
// by choosing an average.
mGeometricScale = avg(mXScale, mYScale);
// Size of diagonal axis.
float diagonalSize = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
// Size factors.
if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
} else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
} else {
mSizeScale = 0.0f;
}
mOrientedRanges.haveTouchSize = true;
mOrientedRanges.haveToolSize = true;
mOrientedRanges.haveSize = true;
mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
mOrientedRanges.touchMajor.source = mSource;
mOrientedRanges.touchMajor.min = 0;
mOrientedRanges.touchMajor.max = diagonalSize;
mOrientedRanges.touchMajor.flat = 0;
mOrientedRanges.touchMajor.fuzz = 0;
mOrientedRanges.touchMajor.resolution = 0;
mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
mOrientedRanges.toolMajor.source = mSource;
mOrientedRanges.toolMajor.min = 0;
mOrientedRanges.toolMajor.max = diagonalSize;
mOrientedRanges.toolMajor.flat = 0;
mOrientedRanges.toolMajor.fuzz = 0;
mOrientedRanges.toolMajor.resolution = 0;
mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
mOrientedRanges.size.source = mSource;
mOrientedRanges.size.min = 0;
mOrientedRanges.size.max = 1.0;
mOrientedRanges.size.flat = 0;
mOrientedRanges.size.fuzz = 0;
mOrientedRanges.size.resolution = 0;
} else {
mSizeScale = 0.0f;
}
// Pressure factors.
mPressureScale = 0;
float pressureMax = 1.0;
if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL ||
mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
if (mCalibration.havePressureScale) {
mPressureScale = mCalibration.pressureScale;
pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
} else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
}
}
mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
mOrientedRanges.pressure.source = mSource;
mOrientedRanges.pressure.min = 0;
mOrientedRanges.pressure.max = pressureMax;
mOrientedRanges.pressure.flat = 0;
mOrientedRanges.pressure.fuzz = 0;
mOrientedRanges.pressure.resolution = 0;
// Tilt
mTiltXCenter = 0;
mTiltXScale = 0;
mTiltYCenter = 0;
mTiltYScale = 0;
mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
if (mHaveTilt) {
mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
mTiltXScale = M_PI / 180;
mTiltYScale = M_PI / 180;
mOrientedRanges.haveTilt = true;
mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
mOrientedRanges.tilt.source = mSource;
mOrientedRanges.tilt.min = 0;
mOrientedRanges.tilt.max = M_PI_2;
mOrientedRanges.tilt.flat = 0;
mOrientedRanges.tilt.fuzz = 0;
mOrientedRanges.tilt.resolution = 0;
}
// Orientation
mOrientationScale = 0;
if (mHaveTilt) {
mOrientedRanges.haveOrientation = true;
mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
mOrientedRanges.orientation.source = mSource;
mOrientedRanges.orientation.min = -M_PI;
mOrientedRanges.orientation.max = M_PI;
mOrientedRanges.orientation.flat = 0;
mOrientedRanges.orientation.fuzz = 0;
mOrientedRanges.orientation.resolution = 0;
} else if (mCalibration.orientationCalibration !=
Calibration::ORIENTATION_CALIBRATION_NONE) {
if (mCalibration.orientationCalibration ==
Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
if (mRawPointerAxes.orientation.valid) {
if (mRawPointerAxes.orientation.maxValue > 0) {
mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
} else if (mRawPointerAxes.orientation.minValue < 0) {
mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
} else {
mOrientationScale = 0;
}
}
}
mOrientedRanges.haveOrientation = true;
mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
mOrientedRanges.orientation.source = mSource;
mOrientedRanges.orientation.min = -M_PI_2;
mOrientedRanges.orientation.max = M_PI_2;
mOrientedRanges.orientation.flat = 0;
mOrientedRanges.orientation.fuzz = 0;
mOrientedRanges.orientation.resolution = 0;
}
// Distance
mDistanceScale = 0;
if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_SCALED) {
if (mCalibration.haveDistanceScale) {
mDistanceScale = mCalibration.distanceScale;
} else {
mDistanceScale = 1.0f;
}
}
mOrientedRanges.haveDistance = true;
mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
mOrientedRanges.distance.source = mSource;
mOrientedRanges.distance.min = mRawPointerAxes.distance.minValue * mDistanceScale;
mOrientedRanges.distance.max = mRawPointerAxes.distance.maxValue * mDistanceScale;
mOrientedRanges.distance.flat = 0;
mOrientedRanges.distance.fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale;
mOrientedRanges.distance.resolution = 0;
}
// Compute oriented precision, scales and ranges.
// Note that the maximum value reported is an inclusive maximum value so it is one
// unit less than the total width or height of surface.
switch (mSurfaceOrientation) {
case DISPLAY_ORIENTATION_90:
case DISPLAY_ORIENTATION_270:
mOrientedXPrecision = mYPrecision;
mOrientedYPrecision = mXPrecision;
mOrientedRanges.x.min = mYTranslate;
mOrientedRanges.x.max = mRawSurfaceHeight + mYTranslate - 1;
mOrientedRanges.x.flat = 0;
mOrientedRanges.x.fuzz = 0;
mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
mOrientedRanges.y.min = mXTranslate;
mOrientedRanges.y.max = mRawSurfaceWidth + mXTranslate - 1;
mOrientedRanges.y.flat = 0;
mOrientedRanges.y.fuzz = 0;
mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
break;
default:
mOrientedXPrecision = mXPrecision;
mOrientedYPrecision = mYPrecision;
mOrientedRanges.x.min = mXTranslate;
mOrientedRanges.x.max = mRawSurfaceWidth + mXTranslate - 1;
mOrientedRanges.x.flat = 0;
mOrientedRanges.x.fuzz = 0;
mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
mOrientedRanges.y.min = mYTranslate;
mOrientedRanges.y.max = mRawSurfaceHeight + mYTranslate - 1;
mOrientedRanges.y.flat = 0;
mOrientedRanges.y.fuzz = 0;
mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
break;
}
// Location
updateAffineTransformation();
if (mDeviceMode == DEVICE_MODE_POINTER) {
// Compute pointer gesture detection parameters.
float rawDiagonal = hypotf(rawWidth, rawHeight);
float displayDiagonal = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
// Scale movements such that one whole swipe of the touch pad covers a
// given area relative to the diagonal size of the display when no acceleration
// is applied.
// Assume that the touch pad has a square aspect ratio such that movements in
// X and Y of the same number of raw units cover the same physical distance.
mPointerXMovementScale =
mConfig.pointerGestureMovementSpeedRatio * displayDiagonal / rawDiagonal;
mPointerYMovementScale = mPointerXMovementScale;
// Scale zooms to cover a smaller range of the display than movements do.
// This value determines the area around the pointer that is affected by freeform
// pointer gestures.
mPointerXZoomScale =
mConfig.pointerGestureZoomSpeedRatio * displayDiagonal / rawDiagonal;
mPointerYZoomScale = mPointerXZoomScale;
// Max width between pointers to detect a swipe gesture is more than some fraction
// of the diagonal axis of the touch pad. Touches that are wider than this are
// translated into freeform gestures.
mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
// Abort current pointer usages because the state has changed.
abortPointerUsage(when, 0 /*policyFlags*/);
}
// Inform the dispatcher about the changes.
*outResetNeeded = true;
bumpGeneration();
}
}
void TouchInputMapper::dumpSurface(std::string& dump) {
dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
dump += StringPrintf(INDENT3 "RawSurfaceWidth: %dpx\n", mRawSurfaceWidth);
dump += StringPrintf(INDENT3 "RawSurfaceHeight: %dpx\n", mRawSurfaceHeight);
dump += StringPrintf(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
dump += StringPrintf(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
dump += StringPrintf(INDENT3 "SurfaceRight: %d\n", mSurfaceRight);
dump += StringPrintf(INDENT3 "SurfaceBottom: %d\n", mSurfaceBottom);
dump += StringPrintf(INDENT3 "PhysicalWidth: %dpx\n", mPhysicalWidth);
dump += StringPrintf(INDENT3 "PhysicalHeight: %dpx\n", mPhysicalHeight);
dump += StringPrintf(INDENT3 "PhysicalLeft: %d\n", mPhysicalLeft);
dump += StringPrintf(INDENT3 "PhysicalTop: %d\n", mPhysicalTop);
dump += StringPrintf(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation);
}
void TouchInputMapper::configureVirtualKeys() {
std::vector<VirtualKeyDefinition> virtualKeyDefinitions;
getDeviceContext().getVirtualKeyDefinitions(virtualKeyDefinitions);
mVirtualKeys.clear();
if (virtualKeyDefinitions.size() == 0) {
return;
}
int32_t touchScreenLeft = mRawPointerAxes.x.minValue;
int32_t touchScreenTop = mRawPointerAxes.y.minValue;
int32_t touchScreenWidth = mRawPointerAxes.getRawWidth();
int32_t touchScreenHeight = mRawPointerAxes.getRawHeight();
for (const VirtualKeyDefinition& virtualKeyDefinition : virtualKeyDefinitions) {
VirtualKey virtualKey;
virtualKey.scanCode = virtualKeyDefinition.scanCode;
int32_t keyCode;
int32_t dummyKeyMetaState;
uint32_t flags;
if (getDeviceContext().mapKey(virtualKey.scanCode, 0, 0, &keyCode, &dummyKeyMetaState,
&flags)) {
ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
continue; // drop the key
}
virtualKey.keyCode = keyCode;
virtualKey.flags = flags;
// convert the key definition's display coordinates into touch coordinates for a hit box
int32_t halfWidth = virtualKeyDefinition.width / 2;
int32_t halfHeight = virtualKeyDefinition.height / 2;
virtualKey.hitLeft =
(virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mRawSurfaceWidth +
touchScreenLeft;
virtualKey.hitRight =
(virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mRawSurfaceWidth +
touchScreenLeft;
virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight /
mRawSurfaceHeight +
touchScreenTop;
virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight /
mRawSurfaceHeight +
touchScreenTop;
mVirtualKeys.push_back(virtualKey);
}
}
void TouchInputMapper::dumpVirtualKeys(std::string& dump) {
if (!mVirtualKeys.empty()) {
dump += INDENT3 "Virtual Keys:\n";
for (size_t i = 0; i < mVirtualKeys.size(); i++) {
const VirtualKey& virtualKey = mVirtualKeys[i];
dump += StringPrintf(INDENT4 "%zu: scanCode=%d, keyCode=%d, "
"hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
i, virtualKey.scanCode, virtualKey.keyCode, virtualKey.hitLeft,
virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
}
}
}
void TouchInputMapper::parseCalibration() {
const PropertyMap& in = getDeviceContext().getConfiguration();
Calibration& out = mCalibration;
// Size
out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
String8 sizeCalibrationString;
if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
if (sizeCalibrationString == "none") {
out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
} else if (sizeCalibrationString == "geometric") {
out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
} else if (sizeCalibrationString == "diameter") {
out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER;
} else if (sizeCalibrationString == "box") {
out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX;
} else if (sizeCalibrationString == "area") {
out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA;
} else if (sizeCalibrationString != "default") {
ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString.string());
}
}
out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"), out.sizeScale);
out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"), out.sizeBias);
out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"), out.sizeIsSummed);
// Pressure
out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
String8 pressureCalibrationString;
if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
if (pressureCalibrationString == "none") {
out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
} else if (pressureCalibrationString == "physical") {
out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
} else if (pressureCalibrationString == "amplitude") {
out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
} else if (pressureCalibrationString != "default") {
ALOGW("Invalid value for touch.pressure.calibration: '%s'",
pressureCalibrationString.string());
}
}
out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"), out.pressureScale);
// Orientation
out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
String8 orientationCalibrationString;
if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
if (orientationCalibrationString == "none") {
out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
} else if (orientationCalibrationString == "interpolated") {
out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
} else if (orientationCalibrationString == "vector") {
out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
} else if (orientationCalibrationString != "default") {
ALOGW("Invalid value for touch.orientation.calibration: '%s'",
orientationCalibrationString.string());
}
}
// Distance
out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
String8 distanceCalibrationString;
if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
if (distanceCalibrationString == "none") {
out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
} else if (distanceCalibrationString == "scaled") {
out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
} else if (distanceCalibrationString != "default") {
ALOGW("Invalid value for touch.distance.calibration: '%s'",
distanceCalibrationString.string());
}
}
out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"), out.distanceScale);
out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
String8 coverageCalibrationString;
if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
if (coverageCalibrationString == "none") {
out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
} else if (coverageCalibrationString == "box") {
out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
} else if (coverageCalibrationString != "default") {
ALOGW("Invalid value for touch.coverage.calibration: '%s'",
coverageCalibrationString.string());
}
}
}
void TouchInputMapper::resolveCalibration() {
// Size
if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) {
mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
}
} else {
mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
}
// Pressure
if (mRawPointerAxes.pressure.valid) {
if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) {
mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
}
} else {
mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
}
// Orientation
if (mRawPointerAxes.orientation.valid) {
if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) {
mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
}
} else {
mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
}
// Distance
if (mRawPointerAxes.distance.valid) {
if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) {
mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
}
} else {
mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
}
// Coverage
if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
}
}
void TouchInputMapper::dumpCalibration(std::string& dump) {
dump += INDENT3 "Calibration:\n";
// Size
switch (mCalibration.sizeCalibration) {
case Calibration::SIZE_CALIBRATION_NONE:
dump += INDENT4 "touch.size.calibration: none\n";
break;
case Calibration::SIZE_CALIBRATION_GEOMETRIC:
dump += INDENT4 "touch.size.calibration: geometric\n";
break;
case Calibration::SIZE_CALIBRATION_DIAMETER:
dump += INDENT4 "touch.size.calibration: diameter\n";
break;
case Calibration::SIZE_CALIBRATION_BOX:
dump += INDENT4 "touch.size.calibration: box\n";
break;
case Calibration::SIZE_CALIBRATION_AREA:
dump += INDENT4 "touch.size.calibration: area\n";
break;
default:
ALOG_ASSERT(false);
}
if (mCalibration.haveSizeScale) {
dump += StringPrintf(INDENT4 "touch.size.scale: %0.3f\n", mCalibration.sizeScale);
}
if (mCalibration.haveSizeBias) {
dump += StringPrintf(INDENT4 "touch.size.bias: %0.3f\n", mCalibration.sizeBias);
}
if (mCalibration.haveSizeIsSummed) {
dump += StringPrintf(INDENT4 "touch.size.isSummed: %s\n",
toString(mCalibration.sizeIsSummed));
}
// Pressure
switch (mCalibration.pressureCalibration) {
case Calibration::PRESSURE_CALIBRATION_NONE:
dump += INDENT4 "touch.pressure.calibration: none\n";
break;
case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
dump += INDENT4 "touch.pressure.calibration: physical\n";
break;
case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
dump += INDENT4 "touch.pressure.calibration: amplitude\n";
break;
default:
ALOG_ASSERT(false);
}
if (mCalibration.havePressureScale) {
dump += StringPrintf(INDENT4 "touch.pressure.scale: %0.3f\n", mCalibration.pressureScale);
}
// Orientation
switch (mCalibration.orientationCalibration) {
case Calibration::ORIENTATION_CALIBRATION_NONE:
dump += INDENT4 "touch.orientation.calibration: none\n";
break;
case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
dump += INDENT4 "touch.orientation.calibration: interpolated\n";
break;
case Calibration::ORIENTATION_CALIBRATION_VECTOR:
dump += INDENT4 "touch.orientation.calibration: vector\n";
break;
default:
ALOG_ASSERT(false);
}
// Distance
switch (mCalibration.distanceCalibration) {
case Calibration::DISTANCE_CALIBRATION_NONE:
dump += INDENT4 "touch.distance.calibration: none\n";
break;
case Calibration::DISTANCE_CALIBRATION_SCALED:
dump += INDENT4 "touch.distance.calibration: scaled\n";
break;
default:
ALOG_ASSERT(false);
}
if (mCalibration.haveDistanceScale) {
dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", mCalibration.distanceScale);
}
switch (mCalibration.coverageCalibration) {
case Calibration::COVERAGE_CALIBRATION_NONE:
dump += INDENT4 "touch.coverage.calibration: none\n";
break;
case Calibration::COVERAGE_CALIBRATION_BOX:
dump += INDENT4 "touch.coverage.calibration: box\n";
break;
default:
ALOG_ASSERT(false);
}
}
void TouchInputMapper::dumpAffineTransformation(std::string& dump) {
dump += INDENT3 "Affine Transformation:\n";
dump += StringPrintf(INDENT4 "X scale: %0.3f\n", mAffineTransform.x_scale);
dump += StringPrintf(INDENT4 "X ymix: %0.3f\n", mAffineTransform.x_ymix);
dump += StringPrintf(INDENT4 "X offset: %0.3f\n", mAffineTransform.x_offset);
dump += StringPrintf(INDENT4 "Y xmix: %0.3f\n", mAffineTransform.y_xmix);
dump += StringPrintf(INDENT4 "Y scale: %0.3f\n", mAffineTransform.y_scale);
dump += StringPrintf(INDENT4 "Y offset: %0.3f\n", mAffineTransform.y_offset);
}
void TouchInputMapper::updateAffineTransformation() {
mAffineTransform = getPolicy()->getTouchAffineTransformation(getDeviceContext().getDescriptor(),
mSurfaceOrientation);
}
void TouchInputMapper::reset(nsecs_t when) {
mCursorButtonAccumulator.reset(getDeviceContext());
mCursorScrollAccumulator.reset(getDeviceContext());
mTouchButtonAccumulator.reset(getDeviceContext());
mPointerVelocityControl.reset();
mWheelXVelocityControl.reset();
mWheelYVelocityControl.reset();
mRawStatesPending.clear();
mCurrentRawState.clear();
mCurrentCookedState.clear();
mLastRawState.clear();
mLastCookedState.clear();
mPointerUsage = POINTER_USAGE_NONE;
mSentHoverEnter = false;
mHavePointerIds = false;
mCurrentMotionAborted = false;
mDownTime = 0;
mCurrentVirtualKey.down = false;
mPointerGesture.reset();
mPointerSimple.reset();
resetExternalStylus();
if (mPointerController != nullptr) {
mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
mPointerController->clearSpots();
}
InputMapper::reset(when);
}
void TouchInputMapper::resetExternalStylus() {
mExternalStylusState.clear();
mExternalStylusId = -1;
mExternalStylusFusionTimeout = LLONG_MAX;
mExternalStylusDataPending = false;
}
void TouchInputMapper::clearStylusDataPendingFlags() {
mExternalStylusDataPending = false;
mExternalStylusFusionTimeout = LLONG_MAX;
}
void TouchInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(rawEvent);
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
}
}
void TouchInputMapper::sync(nsecs_t when) {
// Push a new state.
mRawStatesPending.emplace_back();
RawState& next = mRawStatesPending.back();
next.clear();
next.when = when;
// Sync button state.
next.buttonState =
mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
// Sync scroll
next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
next.rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
mCursorScrollAccumulator.finishSync();
// Sync touch
syncTouch(when, &next);
// The last RawState is the actually second to last, since we just added a new state
const RawState& last =
mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
// Assign pointer ids.
if (!mHavePointerIds) {
assignPointerIds(last, next);
}
#if DEBUG_RAW_EVENTS
ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
"hovering ids 0x%08x -> 0x%08x",
last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value);
#endif
processRawTouches(false /*timeout*/);
}
void TouchInputMapper::processRawTouches(bool timeout) {
if (mDeviceMode == DEVICE_MODE_DISABLED) {
// Drop all input if the device is disabled.
mCurrentRawState.clear();
mRawStatesPending.clear();
return;
}
// Drain any pending touch states. The invariant here is that the mCurrentRawState is always
// valid and must go through the full cook and dispatch cycle. This ensures that anything
// touching the current state will only observe the events that have been dispatched to the
// rest of the pipeline.
const size_t N = mRawStatesPending.size();
size_t count;
for (count = 0; count < N; count++) {
const RawState& next = mRawStatesPending[count];
// A failure to assign the stylus id means that we're waiting on stylus data
// and so should defer the rest of the pipeline.
if (assignExternalStylusId(next, timeout)) {
break;
}
// All ready to go.
clearStylusDataPendingFlags();
mCurrentRawState.copyFrom(next);
if (mCurrentRawState.when < mLastRawState.when) {
mCurrentRawState.when = mLastRawState.when;
}
cookAndDispatch(mCurrentRawState.when);
}
if (count != 0) {
mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
}
if (mExternalStylusDataPending) {
if (timeout) {
nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY;
clearStylusDataPendingFlags();
mCurrentRawState.copyFrom(mLastRawState);
#if DEBUG_STYLUS_FUSION
ALOGD("Timeout expired, synthesizing event with new stylus data");
#endif
cookAndDispatch(when);
} else if (mExternalStylusFusionTimeout == LLONG_MAX) {
mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT;
getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
}
}
}
void TouchInputMapper::cookAndDispatch(nsecs_t when) {
// Always start with a clean state.
mCurrentCookedState.clear();
// Apply stylus buttons to current raw state.
applyExternalStylusButtonState(when);
// Handle policy on initial down or hover events.
bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 &&
mCurrentRawState.rawPointerData.pointerCount != 0;
uint32_t policyFlags = 0;
bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState;
if (initialDown || buttonsPressed) {
// If this is a touch screen, hide the pointer on an initial down.
if (mDeviceMode == DEVICE_MODE_DIRECT) {
getContext()->fadePointer();
}
if (mParameters.wake) {
policyFlags |= POLICY_FLAG_WAKE;
}
}
// Consume raw off-screen touches before cooking pointer data.
// If touches are consumed, subsequent code will not receive any pointer data.
if (consumeRawTouches(when, policyFlags)) {
mCurrentRawState.rawPointerData.clear();
}
// Cook pointer data. This call populates the mCurrentCookedState.cookedPointerData structure
// with cooked pointer data that has the same ids and indices as the raw data.
// The following code can use either the raw or cooked data, as needed.
cookPointerData();
// Apply stylus pressure to current cooked state.
applyExternalStylusTouchState(when);
// Synthesize key down from raw buttons if needed.
synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
mViewport.displayId, policyFlags, mLastCookedState.buttonState,
mCurrentCookedState.buttonState);
// Dispatch the touches either directly or by translation through a pointer on screen.
if (mDeviceMode == DEVICE_MODE_POINTER) {
for (BitSet32 idBits(mCurrentRawState.rawPointerData.touchingIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
mCurrentCookedState.stylusIdBits.markBit(id);
} else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER ||
pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
mCurrentCookedState.fingerIdBits.markBit(id);
} else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
mCurrentCookedState.mouseIdBits.markBit(id);
}
}
for (BitSet32 idBits(mCurrentRawState.rawPointerData.hoveringIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
mCurrentCookedState.stylusIdBits.markBit(id);
}
}
// Stylus takes precedence over all tools, then mouse, then finger.
PointerUsage pointerUsage = mPointerUsage;
if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
mCurrentCookedState.mouseIdBits.clear();
mCurrentCookedState.fingerIdBits.clear();
pointerUsage = POINTER_USAGE_STYLUS;
} else if (!mCurrentCookedState.mouseIdBits.isEmpty()) {
mCurrentCookedState.fingerIdBits.clear();
pointerUsage = POINTER_USAGE_MOUSE;
} else if (!mCurrentCookedState.fingerIdBits.isEmpty() ||
isPointerDown(mCurrentRawState.buttonState)) {
pointerUsage = POINTER_USAGE_GESTURES;
}
dispatchPointerUsage(when, policyFlags, pointerUsage);
} else {
if (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches &&
mPointerController != nullptr) {
mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
mPointerController->setButtonState(mCurrentRawState.buttonState);
mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mCurrentCookedState.cookedPointerData.touchingIdBits,
mViewport.displayId);
}
if (!mCurrentMotionAborted) {
dispatchButtonRelease(when, policyFlags);
dispatchHoverExit(when, policyFlags);
dispatchTouches(when, policyFlags);
dispatchHoverEnterAndMove(when, policyFlags);
dispatchButtonPress(when, policyFlags);
}
if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
mCurrentMotionAborted = false;
}
}
// Synthesize key up from raw buttons if needed.
synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
mViewport.displayId, policyFlags, mLastCookedState.buttonState,
mCurrentCookedState.buttonState);
// Clear some transient state.
mCurrentRawState.rawVScroll = 0;
mCurrentRawState.rawHScroll = 0;
// Copy current touch to last touch in preparation for the next cycle.
mLastRawState.copyFrom(mCurrentRawState);
mLastCookedState.copyFrom(mCurrentCookedState);
}
void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
if (mDeviceMode == DEVICE_MODE_DIRECT && hasExternalStylus() && mExternalStylusId != -1) {
mCurrentRawState.buttonState |= mExternalStylusState.buttons;
}
}
void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) {
CookedPointerData& currentPointerData = mCurrentCookedState.cookedPointerData;
const CookedPointerData& lastPointerData = mLastCookedState.cookedPointerData;
if (mExternalStylusId != -1 && currentPointerData.isTouching(mExternalStylusId)) {
float pressure = mExternalStylusState.pressure;
if (pressure == 0.0f && lastPointerData.isTouching(mExternalStylusId)) {
const PointerCoords& coords = lastPointerData.pointerCoordsForId(mExternalStylusId);
pressure = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
}
PointerCoords& coords = currentPointerData.editPointerCoordsWithId(mExternalStylusId);
coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
PointerProperties& properties =
currentPointerData.editPointerPropertiesWithId(mExternalStylusId);
if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
properties.toolType = mExternalStylusState.toolType;
}
}
}
bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeout) {
if (mDeviceMode != DEVICE_MODE_DIRECT || !hasExternalStylus()) {
return false;
}
const bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 &&
state.rawPointerData.pointerCount != 0;
if (initialDown) {
if (mExternalStylusState.pressure != 0.0f) {
#if DEBUG_STYLUS_FUSION
ALOGD("Have both stylus and touch data, beginning fusion");
#endif
mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit();
} else if (timeout) {
#if DEBUG_STYLUS_FUSION
ALOGD("Timeout expired, assuming touch is not a stylus.");
#endif
resetExternalStylus();
} else {
if (mExternalStylusFusionTimeout == LLONG_MAX) {
mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT;
}
#if DEBUG_STYLUS_FUSION
ALOGD("No stylus data but stylus is connected, requesting timeout "
"(%" PRId64 "ms)",
mExternalStylusFusionTimeout);
#endif
getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
return true;
}
}
// Check if the stylus pointer has gone up.
if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) {
#if DEBUG_STYLUS_FUSION
ALOGD("Stylus pointer is going up");
#endif
mExternalStylusId = -1;
}
return false;
}
void TouchInputMapper::timeoutExpired(nsecs_t when) {
if (mDeviceMode == DEVICE_MODE_POINTER) {
if (mPointerUsage == POINTER_USAGE_GESTURES) {
dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
}
} else if (mDeviceMode == DEVICE_MODE_DIRECT) {
if (mExternalStylusFusionTimeout < when) {
processRawTouches(true /*timeout*/);
} else if (mExternalStylusFusionTimeout != LLONG_MAX) {
getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
}
}
}
void TouchInputMapper::updateExternalStylusState(const StylusState& state) {
mExternalStylusState.copyFrom(state);
if (mExternalStylusId != -1 || mExternalStylusFusionTimeout != LLONG_MAX) {
// We're either in the middle of a fused stream of data or we're waiting on data before
// dispatching the initial down, so go ahead and dispatch now that we have fresh stylus
// data.
mExternalStylusDataPending = true;
processRawTouches(false /*timeout*/);
}
}
bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
// Check for release of a virtual key.
if (mCurrentVirtualKey.down) {
if (mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
// Pointer went up while virtual key was down.
mCurrentVirtualKey.down = false;
if (!mCurrentVirtualKey.ignored) {
#if DEBUG_VIRTUAL_KEYS
ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
}
return true;
}
if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
// Pointer is still within the space of the virtual key.
return true;
}
}
// Pointer left virtual key area or another pointer also went down.
// Send key cancellation but do not consume the touch yet.
// This is useful when the user swipes through from the virtual key area
// into the main display surface.
mCurrentVirtualKey.down = false;
if (!mCurrentVirtualKey.ignored) {
#if DEBUG_VIRTUAL_KEYS
ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode,
mCurrentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY |
AKEY_EVENT_FLAG_CANCELED);
}
}
if (mLastRawState.rawPointerData.touchingIdBits.isEmpty() &&
!mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
// Pointer just went down. Check for virtual key press or off-screen touches.
uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
if (!isPointInsideSurface(pointer.x, pointer.y)) {
// If exactly one pointer went down, check for virtual key hit.
// Otherwise we will drop the entire stroke.
if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
if (virtualKey) {
mCurrentVirtualKey.down = true;
mCurrentVirtualKey.downTime = when;
mCurrentVirtualKey.keyCode = virtualKey->keyCode;
mCurrentVirtualKey.scanCode = virtualKey->scanCode;
mCurrentVirtualKey.ignored =
getContext()->shouldDropVirtualKey(when, virtualKey->keyCode,
virtualKey->scanCode);
if (!mCurrentVirtualKey.ignored) {
#if DEBUG_VIRTUAL_KEYS
ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_DOWN,
AKEY_EVENT_FLAG_FROM_SYSTEM |
AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
}
}
}
return true;
}
}
// Disable all virtual key touches that happen within a short time interval of the
// most recent touch within the screen area. The idea is to filter out stray
// virtual key presses when interacting with the touch screen.
//
// Problems we're trying to solve:
//
// 1. While scrolling a list or dragging the window shade, the user swipes down into a
// virtual key area that is implemented by a separate touch panel and accidentally
// triggers a virtual key.
//
// 2. While typing in the on screen keyboard, the user taps slightly outside the screen
// area and accidentally triggers a virtual key. This often happens when virtual keys
// are layed out below the screen near to where the on screen keyboard's space bar
// is displayed.
if (mConfig.virtualKeyQuietTime > 0 &&
!mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
getContext()->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime);
}
return false;
}
void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
int32_t keyEventAction, int32_t keyEventFlags) {
int32_t keyCode = mCurrentVirtualKey.keyCode;
int32_t scanCode = mCurrentVirtualKey.scanCode;
nsecs_t downTime = mCurrentVirtualKey.downTime;
int32_t metaState = getContext()->getGlobalMetaState();
policyFlags |= POLICY_FLAG_VIRTUAL;
NotifyKeyArgs args(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
mViewport.displayId, policyFlags, keyEventAction, keyEventFlags, keyCode,
scanCode, metaState, downTime);
getListener()->notifyKey(&args);
}
void TouchInputMapper::abortTouches(nsecs_t when, uint32_t policyFlags) {
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
if (!currentIdBits.isEmpty()) {
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
mCurrentMotionAborted = true;
}
}
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
if (currentIdBits == lastIdBits) {
if (!currentIdBits.isEmpty()) {
// No pointer id changes so this is a move event.
// The listener takes care of batching moves so we don't have to deal with that here.
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
} else {
// There may be pointers going up and pointers going down and pointers moving
// all at the same time.
BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
BitSet32 dispatchedIdBits(lastIdBits.value);
// Update last coordinates of pointers that have moved so that we observe the new
// pointer positions at the same time as other pointers that have just gone up.
bool moveNeeded =
updateMovedPointers(mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex, moveIdBits);
if (buttonState != mLastCookedState.buttonState) {
moveNeeded = true;
}
// Dispatch pointer up events.
while (!upIdBits.isEmpty()) {
uint32_t upId = upIdBits.clearFirstMarkedBit();
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, 0,
metaState, buttonState, 0,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
dispatchedIdBits.clearBit(upId);
}
// Dispatch move events if any of the remaining pointers moved from their old locations.
// Although applications receive new locations as part of individual pointer up
// events, they do not generally handle them except when presented in a move event.
if (moveNeeded && !moveIdBits.isEmpty()) {
ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
// Dispatch pointer down events using the new pointer locations.
while (!downIdBits.isEmpty()) {
uint32_t downId = downIdBits.clearFirstMarkedBit();
dispatchedIdBits.markBit(downId);
if (dispatchedIdBits.count() == 1) {
// First pointer is going down. Set down time.
mDownTime = when;
}
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
}
}
void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
if (mSentHoverEnter &&
(mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() ||
!mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) {
int32_t metaState = getContext()->getGlobalMetaState();
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
mLastCookedState.buttonState, 0,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex,
mLastCookedState.cookedPointerData.hoveringIdBits, -1, mOrientedXPrecision,
mOrientedYPrecision, mDownTime);
mSentHoverEnter = false;
}
}
void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
if (mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty() &&
!mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) {
int32_t metaState = getContext()->getGlobalMetaState();
if (!mSentHoverEnter) {
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
metaState, mCurrentRawState.buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mCurrentCookedState.cookedPointerData.hoveringIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
mSentHoverEnter = true;
}
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
mCurrentRawState.buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mCurrentCookedState.cookedPointerData.hoveringIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
}
void TouchInputMapper::dispatchButtonRelease(nsecs_t when, uint32_t policyFlags) {
BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData);
const int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mLastCookedState.buttonState;
while (!releasedButtons.isEmpty()) {
int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit());
buttonState &= ~actionButton;
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
actionButton, 0, metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
}
void TouchInputMapper::dispatchButtonPress(nsecs_t when, uint32_t policyFlags) {
BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData);
const int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mLastCookedState.buttonState;
while (!pressedButtons.isEmpty()) {
int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit());
buttonState |= actionButton;
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton,
0, metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
}
const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) {
if (!cookedPointerData.touchingIdBits.isEmpty()) {
return cookedPointerData.touchingIdBits;
}
return cookedPointerData.hoveringIdBits;
}
void TouchInputMapper::cookPointerData() {
uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;
mCurrentCookedState.cookedPointerData.clear();
mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
mCurrentCookedState.cookedPointerData.hoveringIdBits =
mCurrentRawState.rawPointerData.hoveringIdBits;
mCurrentCookedState.cookedPointerData.touchingIdBits =
mCurrentRawState.rawPointerData.touchingIdBits;
if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
mCurrentCookedState.buttonState = 0;
} else {
mCurrentCookedState.buttonState = mCurrentRawState.buttonState;
}
// Walk through the the active pointers and map device coordinates onto
// surface coordinates and adjust for display orientation.
for (uint32_t i = 0; i < currentPointerCount; i++) {
const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
// Size
float touchMajor, touchMinor, toolMajor, toolMinor, size;
switch (mCalibration.sizeCalibration) {
case Calibration::SIZE_CALIBRATION_GEOMETRIC:
case Calibration::SIZE_CALIBRATION_DIAMETER:
case Calibration::SIZE_CALIBRATION_BOX:
case Calibration::SIZE_CALIBRATION_AREA:
if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
touchMajor = in.touchMajor;
touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
toolMajor = in.toolMajor;
toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
: in.touchMajor;
} else if (mRawPointerAxes.touchMajor.valid) {
toolMajor = touchMajor = in.touchMajor;
toolMinor = touchMinor =
mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
: in.touchMajor;
} else if (mRawPointerAxes.toolMajor.valid) {
touchMajor = toolMajor = in.toolMajor;
touchMinor = toolMinor =
mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
size = mRawPointerAxes.toolMinor.valid ? avg(in.toolMajor, in.toolMinor)
: in.toolMajor;
} else {
ALOG_ASSERT(false,
"No touch or tool axes. "
"Size calibration should have been resolved to NONE.");
touchMajor = 0;
touchMinor = 0;
toolMajor = 0;
toolMinor = 0;
size = 0;
}
if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) {
uint32_t touchingCount = mCurrentRawState.rawPointerData.touchingIdBits.count();
if (touchingCount > 1) {
touchMajor /= touchingCount;
touchMinor /= touchingCount;
toolMajor /= touchingCount;
toolMinor /= touchingCount;
size /= touchingCount;
}
}
if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) {
touchMajor *= mGeometricScale;
touchMinor *= mGeometricScale;
toolMajor *= mGeometricScale;
toolMinor *= mGeometricScale;
} else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) {
touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0;
touchMinor = touchMajor;
toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0;
toolMinor = toolMajor;
} else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) {
touchMinor = touchMajor;
toolMinor = toolMajor;
}
mCalibration.applySizeScaleAndBias(&touchMajor);
mCalibration.applySizeScaleAndBias(&touchMinor);
mCalibration.applySizeScaleAndBias(&toolMajor);
mCalibration.applySizeScaleAndBias(&toolMinor);
size *= mSizeScale;
break;
default:
touchMajor = 0;
touchMinor = 0;
toolMajor = 0;
toolMinor = 0;
size = 0;
break;
}
// Pressure
float pressure;
switch (mCalibration.pressureCalibration) {
case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
pressure = in.pressure * mPressureScale;
break;
default:
pressure = in.isHovering ? 0 : 1;
break;
}
// Tilt and Orientation
float tilt;
float orientation;
if (mHaveTilt) {
float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
} else {
tilt = 0;
switch (mCalibration.orientationCalibration) {
case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
orientation = in.orientation * mOrientationScale;
break;
case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
int32_t c2 = signExtendNybble(in.orientation & 0x0f);
if (c1 != 0 || c2 != 0) {
orientation = atan2f(c1, c2) * 0.5f;
float confidence = hypotf(c1, c2);
float scale = 1.0f + confidence / 16.0f;
touchMajor *= scale;
touchMinor /= scale;
toolMajor *= scale;
toolMinor /= scale;
} else {
orientation = 0;
}
break;
}
default:
orientation = 0;
}
}
// Distance
float distance;
switch (mCalibration.distanceCalibration) {
case Calibration::DISTANCE_CALIBRATION_SCALED:
distance = in.distance * mDistanceScale;
break;
default:
distance = 0;
}
// Coverage
int32_t rawLeft, rawTop, rawRight, rawBottom;
switch (mCalibration.coverageCalibration) {
case Calibration::COVERAGE_CALIBRATION_BOX:
rawLeft = (in.toolMinor & 0xffff0000) >> 16;
rawRight = in.toolMinor & 0x0000ffff;
rawBottom = in.toolMajor & 0x0000ffff;
rawTop = (in.toolMajor & 0xffff0000) >> 16;
break;
default:
rawLeft = rawTop = rawRight = rawBottom = 0;
break;
}
// Adjust X,Y coords for device calibration
// TODO: Adjust coverage coords?
float xTransformed = in.x, yTransformed = in.y;
mAffineTransform.applyTo(xTransformed, yTransformed);
rotateAndScale(xTransformed, yTransformed);
// Adjust X, Y, and coverage coords for surface orientation.
float left, top, right, bottom;
switch (mSurfaceOrientation) {
case DISPLAY_ORIENTATION_90:
left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
orientation -= M_PI_2;
if (mOrientedRanges.haveOrientation &&
orientation < mOrientedRanges.orientation.min) {
orientation +=
(mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
case DISPLAY_ORIENTATION_180:
left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
orientation -= M_PI;
if (mOrientedRanges.haveOrientation &&
orientation < mOrientedRanges.orientation.min) {
orientation +=
(mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
case DISPLAY_ORIENTATION_270:
left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
orientation += M_PI_2;
if (mOrientedRanges.haveOrientation &&
orientation > mOrientedRanges.orientation.max) {
orientation -=
(mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
default:
left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
break;
}
// Write output coords.
PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
out.clear();
out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed);
out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed);
out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
} else {
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
}
// Write output properties.
PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
uint32_t id = in.id;
properties.clear();
properties.id = id;
properties.toolType = in.toolType;
// Write id index.
mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
}
}
void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
PointerUsage pointerUsage) {
if (pointerUsage != mPointerUsage) {
abortPointerUsage(when, policyFlags);
mPointerUsage = pointerUsage;
}
switch (mPointerUsage) {
case POINTER_USAGE_GESTURES:
dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
break;
case POINTER_USAGE_STYLUS:
dispatchPointerStylus(when, policyFlags);
break;
case POINTER_USAGE_MOUSE:
dispatchPointerMouse(when, policyFlags);
break;
default:
break;
}
}
void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
switch (mPointerUsage) {
case POINTER_USAGE_GESTURES:
abortPointerGestures(when, policyFlags);
break;
case POINTER_USAGE_STYLUS:
abortPointerStylus(when, policyFlags);
break;
case POINTER_USAGE_MOUSE:
abortPointerMouse(when, policyFlags);
break;
default:
break;
}
mPointerUsage = POINTER_USAGE_NONE;
}
void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout) {
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
bool sendEvents =
preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
if (!sendEvents) {
return;
}
if (finishPreviousGesture) {
cancelPreviousGesture = false;
}
// Update the pointer presentation and spots.
if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH) {
mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
if (finishPreviousGesture || cancelPreviousGesture) {
mPointerController->clearSpots();
}
if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
mPointerController->setSpots(mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex,
mPointerGesture.currentGestureIdBits,
mPointerController->getDisplayId());
}
} else {
mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
}
// Show or hide the pointer if needed.
switch (mPointerGesture.currentGestureMode) {
case PointerGesture::NEUTRAL:
case PointerGesture::QUIET:
if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH &&
mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) {
// Remind the user of where the pointer is after finishing a gesture with spots.
mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
}
break;
case PointerGesture::TAP:
case PointerGesture::TAP_DRAG:
case PointerGesture::BUTTON_CLICK_OR_DRAG:
case PointerGesture::HOVER:
case PointerGesture::PRESS:
case PointerGesture::SWIPE:
// Unfade the pointer when the current gesture manipulates the
// area directly under the pointer.
mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
break;
case PointerGesture::FREEFORM:
// Fade the pointer when the current gesture manipulates a different
// area and there are spots to guide the user experience.
if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH) {
mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
} else {
mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
}
break;
}
// Send events!
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
// Update last coordinates of pointers that have moved so that we observe the new
// pointer positions at the same time as other pointers that have just gone up.
bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP ||
mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG ||
mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG ||
mPointerGesture.currentGestureMode == PointerGesture::PRESS ||
mPointerGesture.currentGestureMode == PointerGesture::SWIPE ||
mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
bool moveNeeded = false;
if (down && !cancelPreviousGesture && !finishPreviousGesture &&
!mPointerGesture.lastGestureIdBits.isEmpty() &&
!mPointerGesture.currentGestureIdBits.isEmpty()) {
BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value &
mPointerGesture.lastGestureIdBits.value);
moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties,
mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex,
mPointerGesture.lastGestureProperties,
mPointerGesture.lastGestureCoords,
mPointerGesture.lastGestureIdToIndex, movedGestureIdBits);
if (buttonState != mLastCookedState.buttonState) {
moveNeeded = true;
}
}
// Send motion events for all pointers that went up or were canceled.
BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
if (!dispatchedGestureIdBits.isEmpty()) {
if (cancelPreviousGesture) {
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
mPointerGesture.downTime);
dispatchedGestureIdBits.clear();
} else {
BitSet32 upGestureIdBits;
if (finishPreviousGesture) {
upGestureIdBits = dispatchedGestureIdBits;
} else {
upGestureIdBits.value =
dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value;
}
while (!upGestureIdBits.isEmpty()) {
uint32_t id = upGestureIdBits.clearFirstMarkedBit();
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, 0,
metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.lastGestureProperties,
mPointerGesture.lastGestureCoords,
mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0,
0, mPointerGesture.downTime);
dispatchedGestureIdBits.clearBit(id);
}
}
}
// Send motion events for all pointers that moved.
if (moveNeeded) {
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.currentGestureProperties,
mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
mPointerGesture.downTime);
}
// Send motion events for all pointers that went down.
if (down) {
BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value &
~dispatchedGestureIdBits.value);
while (!downGestureIdBits.isEmpty()) {
uint32_t id = downGestureIdBits.clearFirstMarkedBit();
dispatchedGestureIdBits.markBit(id);
if (dispatchedGestureIdBits.count() == 1) {
mPointerGesture.downTime = when;
}
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
metaState, buttonState, 0, mPointerGesture.currentGestureProperties,
mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0,
0, mPointerGesture.downTime);
}
}
// Send motion events for hover.
if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.currentGestureProperties,
mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex,
mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime);
} else if (dispatchedGestureIdBits.isEmpty() && !mPointerGesture.lastGestureIdBits.isEmpty()) {
// Synthesize a hover move event after all pointers go up to indicate that
// the pointer is hovering again even if the user is not currently touching
// the touch pad. This ensures that a view will receive a fresh hover enter
// event after a tap.
float x, y;
mPointerController->getPosition(&x, &y);
PointerProperties pointerProperties;
pointerProperties.clear();
pointerProperties.id = 0;
pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
PointerCoords pointerCoords;
pointerCoords.clear();
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
const int32_t displayId = mPointerController->getDisplayId();
NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
1, &pointerProperties, &pointerCoords, 0, 0, x, y,
mPointerGesture.downTime, /* videoFrames */ {});
getListener()->notifyMotion(&args);
}
// Update state.
mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
if (!down) {
mPointerGesture.lastGestureIdBits.clear();
} else {
mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
mPointerGesture.lastGestureProperties[index].copyFrom(
mPointerGesture.currentGestureProperties[index]);
mPointerGesture.lastGestureCoords[index].copyFrom(
mPointerGesture.currentGestureCoords[index]);
mPointerGesture.lastGestureIdToIndex[id] = index;
}
}
}
void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
// Cancel previously dispatches pointers.
if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentRawState.buttonState;
dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1,
0, 0, mPointerGesture.downTime);
}
// Reset the current pointer gesture.
mPointerGesture.reset();
mPointerVelocityControl.reset();
// Remove any current spots.
if (mPointerController != nullptr) {
mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
mPointerController->clearSpots();
}
}
bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
bool* outFinishPreviousGesture, bool isTimeout) {
*outCancelPreviousGesture = false;
*outFinishPreviousGesture = false;
// Handle TAP timeout.
if (isTimeout) {
#if DEBUG_GESTURES
ALOGD("Gestures: Processing timeout");
#endif
if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
// The tap/drag timeout has not yet expired.
getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime +
mConfig.pointerGestureTapDragInterval);
} else {
// The tap is finished.
#if DEBUG_GESTURES
ALOGD("Gestures: TAP finished");
#endif
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
mPointerGesture.currentGestureIdBits.clear();
mPointerVelocityControl.reset();
return true;
}
}
// We did not handle this timeout.
return false;
}
const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
// Update the velocity tracker.
{
VelocityTracker::Position positions[MAX_POINTERS];
uint32_t count = 0;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty(); count++) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
positions[count].x = pointer.x * mPointerXMovementScale;
positions[count].y = pointer.y * mPointerYMovementScale;
}
mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
positions);
}
// If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
// to NEUTRAL, then we should not generate tap event.
if (mPointerGesture.lastGestureMode != PointerGesture::HOVER &&
mPointerGesture.lastGestureMode != PointerGesture::TAP &&
mPointerGesture.lastGestureMode != PointerGesture::TAP_DRAG) {
mPointerGesture.resetTap();
}
// Pick a new active touch id if needed.
// Choose an arbitrary pointer that just went down, if there is one.
// Otherwise choose an arbitrary remaining pointer.
// This guarantees we always have an active touch id when there is at least one pointer.
// We keep the same active touch id for as long as possible.
int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
int32_t activeTouchId = lastActiveTouchId;
if (activeTouchId < 0) {
if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
activeTouchId = mPointerGesture.activeTouchId =
mCurrentCookedState.fingerIdBits.firstMarkedBit();
mPointerGesture.firstTouchTime = when;
}
} else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) {
if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
activeTouchId = mPointerGesture.activeTouchId =
mCurrentCookedState.fingerIdBits.firstMarkedBit();
} else {
activeTouchId = mPointerGesture.activeTouchId = -1;
}
}
// Determine whether we are in quiet time.
bool isQuietTime = false;
if (activeTouchId < 0) {
mPointerGesture.resetQuietTime();
} else {
isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
if (!isQuietTime) {
if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS ||
mPointerGesture.lastGestureMode == PointerGesture::SWIPE ||
mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) &&
currentFingerCount < 2) {
// Enter quiet time when exiting swipe or freeform state.
// This is to prevent accidentally entering the hover state and flinging the
// pointer when finishing a swipe and there is still one pointer left onscreen.
isQuietTime = true;
} else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG &&
currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
// Enter quiet time when releasing the button and there are still two or more
// fingers down. This may indicate that one finger was used to press the button
// but it has not gone up yet.
isQuietTime = true;
}
if (isQuietTime) {
mPointerGesture.quietTime = when;
}
}
}
// Switch states based on button and pointer state.
if (isQuietTime) {
// Case 1: Quiet time. (QUIET)
#if DEBUG_GESTURES
ALOGD("Gestures: QUIET for next %0.3fms",
(mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * 0.000001f);
#endif
if (mPointerGesture.lastGestureMode != PointerGesture::QUIET) {
*outFinishPreviousGesture = true;
}
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::QUIET;
mPointerGesture.currentGestureIdBits.clear();
mPointerVelocityControl.reset();
} else if (isPointerDown(mCurrentRawState.buttonState)) {
// Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
// The pointer follows the active touch point.
// Emit DOWN, MOVE, UP events at the pointer location.
//
// Only the active touch matters; other fingers are ignored. This policy helps
// to handle the case where the user places a second finger on the touch pad
// to apply the necessary force to depress an integrated button below the surface.
// We don't want the second finger to be delivered to applications.
//
// For this to work well, we need to make sure to track the pointer that is really
// active. If the user first puts one finger down to click then adds another
// finger to drag then the active pointer should switch to the finger that is
// being dragged.
#if DEBUG_GESTURES
ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
"currentFingerCount=%d",
activeTouchId, currentFingerCount);
#endif
// Reset state when just starting.
if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = 0;
}
// Switch pointers if needed.
// Find the fastest pointer and follow it.
if (activeTouchId >= 0 && currentFingerCount > 1) {
int32_t bestId = -1;
float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
float vx, vy;
if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
float speed = hypotf(vx, vy);
if (speed > bestSpeed) {
bestId = id;
bestSpeed = speed;
}
}
}
if (bestId >= 0 && bestId != activeTouchId) {
mPointerGesture.activeTouchId = activeTouchId = bestId;
#if DEBUG_GESTURES
ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
"bestId=%d, bestSpeed=%0.3f",
bestId, bestSpeed);