【Android】输入路由
Android,可以通过配置 input-port-associations.xml
,将Display和Input(屏幕输入)节点关联起来。
<ports>
<port display="0" input="linduoDev-1.0/input0" />
<port display="1" input="linduoDev-2.0/input0" />
</ports>
display="0"
指定屏幕接口,input="linduoDev-1.0/input0"
指定输入设备接口。可以通过下述命令查看配置关系
adb shell dumpsys input
相关源码流程
解析input-port-associations.xml
- IMS(InputManagerService.java)
private static final String PORT_ASSOCIATIONS_PATH =
"etc/input-port-associations.xml";
private static Map<String, Integer> loadStaticInputPortAssociations() {
final File baseDir = Environment.getVendorDirectory();
final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
try {
final InputStream stream = new FileInputStream(confFile);
return ConfigurationProcessor.processInputPortAssociations(stream);
} catch (FileNotFoundException e) {
// Most of the time, file will not exist, which is expected.
} catch (Exception e) {
Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e);
}
return new HashMap<>();
}
- 解析的内容后的格式为
<key>:InputPort
,<value>:DipslayPort
@VisibleForTesting
static Map<String, Integer> processInputPortAssociations(InputStream xml) throws Exception {
Map<String, Integer> associations = new HashMap<String, Integer>();
{
TypedXmlPullParser parser = Xml.resolvePullParser(xml);
XmlUtils.beginDocument(parser, "ports");
while (true) {
XmlUtils.nextElement(parser);
String entryName = parser.getName();
if (!"port".equals(entryName)) {
break;
}
String inputPort = parser.getAttributeValue(null, "input");
String displayPortStr = parser.getAttributeValue(null, "display");
if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPortStr)) {
continue;
}
try {
int displayPort = Integer.parseUnsignedInt(displayPortStr);
associations.put(inputPort, displayPort);
} catch (NumberFormatException e) {
Slog.wtf(TAG, "Display port should be an integer");
}
}
}
return associations;
}
- IMS通过JNI Callback,将InputPort:DisplayPort信息,传输到IMS的Native层。
// Native callback
private String[] getInputPortAssociations() {
final Map<String, Integer> associations = new HashMap<>(mStaticAssociations);
// merge the runtime associations.synchronized
(mAssociationsLock) {
associations.putAll(mRuntimeAssociations);
}
return flatten(associations);
}
- JNI代码对应(com_android_server_input_InputManagerService.cpp)。读取InputPort、DisplayPort的配置。并设置DisplayViewPort
void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
ATRACE_CALL();
// Associations between input ports and display ports
// The java method packs the information in the following manner:
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.outConfig->portAssociations.clear();
jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
jsize length = env->GetArrayLength(portAssociations);
for (jsize i = 0; i < length / 2; i++) {
std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
std::string displayPortStr =
getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
uint8_t displayPort;
// Should already have been validated earlier, but do it here for safety.bool success = ParseUint(displayPortStr, &displayPort);
if (!success) {
ALOGE("Could not parse entry in port configuration file, received: %s",
displayPortStr.c_str());
continue;
}
outConfig->portAssociations.insert({inputPort, displayPort});
}
env->DeleteLocalRef(portAssociations);
}
{ // acquire lockAutoMutex _l(mLock);
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->showTouches = mLocked.showTouches;
outConfig->pointerCapture = mLocked.pointerCapture;
outConfig->setDisplayViewports(mLocked.viewports);
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
outConfig->disabledDevices = mLocked.disabledInputDevices;
} // release lock
}
- InputReader通过调用getReaderConfiguration获取到配置信息。然后通知InputDevice配置信息。
void InputReader::refreshConfigurationLocked(uint32_t changes) {
mPolicy->getReaderConfiguration(&mConfig);
mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
if (!changes) return;
ALOGI("Reconfiguring input devices, changes=%s",
InputReaderConfiguration::changesToString(changes).c_str());
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) {
updatePointerDisplayLocked();
}
if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
mEventHub->requestReopenDevices();
} else {
for (auto& devicePair : mDevices) {
std::shared_ptr<InputDevice>& device = devicePair.second;
device->configure(now, &mConfig, changes);
}
}
if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) {
const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
mConfig.pointerCapture);
mQueuedListener->notifyPointerCaptureChanged(&args);
}
}
- 在InputDevice的configure中。拿到InputDevice关联的ViewPort
void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
uint32_t changes) {
if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
bool enabled = (config->disabledDevices.find(mId)
== config->disabledDevices.end());
if (mAssociatedDisplayPort) {
mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort);
} else if (mAssociatedDisplayUniqueId != std::nullopt) {
mAssociatedViewport =
config->getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
}
if (changes) {
// For first-time configuration, only allow device to be disabled after mappers have
// finished configuring. This is because we need to read some of the properties from
// the device's open fd.
setEnabled(enabled, when);
}
}
}
- DisplayViewPort为Input派发提供信息,该数据结构信息如下
enum class ViewportType : int32_t {
INTERNAL = 1,
EXTERNAL = 2,
VIRTUAL = 3,
};
/*
* Describes how coordinates are mapped on a physical display.
* See com.android.server.display.DisplayViewport.
*/struct DisplayViewport {
int32_t displayId; // -1 if invalidint32_t orientation;
int32_t logicalLeft;
int32_t logicalTop;
int32_t logicalRight;
int32_t logicalBottom;
int32_t physicalLeft;
int32_t physicalTop;
int32_t physicalRight;
int32_t physicalBottom;
int32_t deviceWidth;
int32_t deviceHeight;
bool isActive;
std::string uniqueId;
// The actual (hardware) port that the associated display is connected to.// Not all viewports will have this specified.std::optional<uint8_t> physicalPort;
ViewportType type;
}
- InputReader会根据InputPort找到对应的InputDevice,将对应的Input信息传输给InputDevice,然后InputDevice根据自己的DisplayViewPort,将Touch派发给对应的DisplayID(TouchInputMapper.cpp)
void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
uint32_t source, int32_t action, int32_t actionButton,
int32_t flags, int32_t metaState, int32_t buttonState,
int32_t edgeFlags, const PointerProperties* properties,
const PointerCoords* coords, const uint32_t* idToIndex,
BitSet32 idBits, int32_t changedId, float xPrecision,
float yPrecision, nsecs_t downTime) {
const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const int32_t deviceId = getDeviceId();
std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
std::for_each(frames.begin(), frames.end(),
[this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
policyFlags, action, actionButton, flags, metaState, buttonState,
MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
downTime, std::move(frames));
getListener()->notifyMotion(&args);
}