Android6.0キーフロー(2)KeyboardInputMapperスキャンコードをキーボードコードに変換
18817 ワード
前回のブログでは、InputReaderでデバイスイベントを読み取り、processEventsLocked関数でイベント(デバイスイベント、デバイス追加、削除などを含む)を処理し、Process EventsForDeviceLockedでデバイスイベントを処理し、最後にInputDeviceのprocess関数まで、各InputMapperを巡ってプロセス関数を実行しました.
今日はここから分析を続けます.Inputデバイスには多くの種類があり、そのメッセージフォーマットがそれぞれ異なるため、さまざまなRawEventを処理するInputMapperがたくさんあります.
今日は主にキーのKeyboardInputMaperから説明します.
次に、音量キー、パワーキーなどのキーを押したこのInputMapperからお話しします.
まずEventHubのmapKey関数を見てみましょう.この関数ではoutKeycodeはパラメータですが、ポインタが伝わっていて、値が書かれています.
やはりOpenDeviceLockedには上のコードがあります.
さらにload関数を見てみましょう.まずdeviceConfigurationでkeyLayoutNameを取得します.deviceConfigurationも前のopenDeviceLockedの時に作成されたに違いありません.ここでは分析しません.
まずgetPath関数を解析してpathをどのように取得するかを見てみましょう
システム/usrディレクトリの下にgetInputDeviceConfigurationFilePathByName関数を見てみましょう
CONFIGURATION_FILE_DIRは各typeに対応する各ディレクトリである
携帯電話のファイルと結びつけてみましょう.
ジェネリックを見てみましょうklというファイルの切り取り部分は、スキャンコードに対応するキー値です.
私たちは引き続きKeyLayoutMap::loadという関数を見て、この関数は詳しく話しません.
次にKeyboardInputMapper::processのprocessKey関数を見てみましょう
この関数は,最初はスキャンコードをキーボードコードに変換し,その後は主にnotifyKey関数を呼び出した.
getListener関数は次のとおりです.
notifyKey関数は、オブジェクトを変数に配置することです.
最後にInputReaderのloopOnce関数に戻り、主に最後のmQueuedListener->flush関数を見ます.
QueuedInputListener::flush関数はファイルInputListener.cpp中.
この関数では,以前に各InputMapperに格納されていたNotifyArgsオブジェクトを巡回し,最後にNotify関数を呼び出した.
以前、私たちのNotifyArgsオブジェクトはNotifyKeyArgsオブジェクトでした.このnotifyは虚関数で、NotifyKeyArgs::notify
このlistenerは、QueuedInputListenerを新築したときに伝わってきました
ListenerはInputReaderから送られてきたので、InputReaderがInputManagerで作成されたことを知っています.
InputManagerのコンストラクション関数、転送されたInputDispatcherを見てみましょう
最後にこのlistenerはInputDispatcherであるため,最後に呼び出されたInputDispatcherのnotifyKey関数である.
一方,InputDispatcherのnotifyKey関数については,次のブログで分析する.
今日はここから分析を続けます.Inputデバイスには多くの種類があり、そのメッセージフォーマットがそれぞれ異なるため、さまざまなRawEventを処理するInputMapperがたくさんあります.
今日は主にキーのKeyboardInputMaperから説明します.
一、KeyboardInputMapper
次に、音量キー、パワーキーなどのキーを押したこのInputMapperからお話しします.
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {
int32_t scanCode = rawEvent->code;
int32_t usageCode = mCurrentHidUsage;
mCurrentHidUsage = 0;
if (isKeyboardOrGamepadKey(scanCode)) {
int32_t keyCode;
uint32_t flags;
if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {//
keyCode = AKEYCODE_UNKNOWN;
flags = 0;
}
processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
}
break;
}
case EV_MSC: {
if (rawEvent->code == MSC_SCAN) {
mCurrentHidUsage = rawEvent->value;
}
break;
}
case EV_SYN: {
if (rawEvent->code == SYN_REPORT) {
mCurrentHidUsage = 0;
}
}
}
}
1.1 EventHubのmapKey関数
まずEventHubのmapKey関数を見てみましょう.この関数ではoutKeycodeはパラメータですが、ポインタが伝わっていて、値が書かれています.
status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t* outKeycode, uint32_t* outFlags) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device) {
// Check the key character map first.
sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
if (kcm != NULL) {
if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
scanCode, *outKeycode);
*outFlags = 0;
return NO_ERROR;
}
}
// Check the key layout next.
if (device->keyMap.haveKeyLayout()) {
if (!device->keyMap.keyLayoutMap->mapKey(
scanCode, usageCode, outKeycode, outFlags)) {
return NO_ERROR;
}
}
}
*outKeycode = 0;
*outFlags = 0;
return NAME_NOT_FOUND;
}
deviceのメンバー変数を見ても、EventHubのopenDeviceLocked関数に間違いないことがわかります. status_t keyMapStatus = NAME_NOT_FOUND;
if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
// Load the keymap for the device.
keyMapStatus = loadKeyMapLocked(device);
}
やはりOpenDeviceLockedには上のコードがあります.
status_t EventHub::loadKeyMapLocked(Device* device) {
return device->keyMap.load(device->identifier, device->configuration);
}
さらにload関数を見てみましょう.まずdeviceConfigurationでkeyLayoutNameを取得します.deviceConfigurationも前のopenDeviceLockedの時に作成されたに違いありません.ここでは分析しません.
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
const PropertyMap* deviceConfiguration) {
// Use the configured key layout if available.
if (deviceConfiguration) {
String8 keyLayoutName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
keyLayoutName)) {// deviceConfiguration keyLayoutName
status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
"it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
"map '%s' but it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
if (isComplete()) {
return OK;
}
}
// Try searching by device identifier.
if (probeKeyMap(deviceIdenfifier, String8::empty())) {
return OK;
}
// Fall back on the Generic key map.
// TODO Apply some additional heuristics here to figure out what kind of
// generic key map to use (US English, etc.) for typical external keyboards.
if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
return OK;
}
// Try the Virtual key map as a last resort.
if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
return OK;
}
// Give up!
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
deviceIdenfifier.name.string());
return NAME_NOT_FOUND;
}
loadKeyLayout関数を見てみましょうstatus_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
const String8& name) {
String8 path(getPath(deviceIdentifier, name,
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
if (path.isEmpty()) {
return NAME_NOT_FOUND;
}
status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
if (status) {
return status;
}
keyLayoutFile.setTo(path);
return OK;
}
まずgetPath関数を解析してpathをどのように取得するかを見てみましょう
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
const String8& name, InputDeviceConfigurationFileType type) {
return name.isEmpty()
? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
: getInputDeviceConfigurationFilePathByName(name, type);
}
システム/usrディレクトリの下にgetInputDeviceConfigurationFilePathByName関数を見てみましょう
String8 getInputDeviceConfigurationFilePathByName(
const String8& name, InputDeviceConfigurationFileType type) {
// Search system repository.
String8 path;
path.setTo(getenv("ANDROID_ROOT"));
path.append("/usr/");
appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
#endif
if (!access(path.string(), R_OK)) {
#if DEBUG_PROBE
ALOGD("Found");
#endif
return path;
}
appendInputDeviceConfigurationFileRelativePath関数を参照static void appendInputDeviceConfigurationFileRelativePath(String8& path,
const String8& name, InputDeviceConfigurationFileType type) {
path.append(CONFIGURATION_FILE_DIR[type]);
for (size_t i = 0; i < name.length(); i++) {
char ch = name[i];
if (!isValidNameChar(ch)) {
ch = '_';
}
path.append(&ch, 1);
}
path.append(CONFIGURATION_FILE_EXTENSION[type]);
}
CONFIGURATION_FILE_DIRは各typeに対応する各ディレクトリである
static const char* CONFIGURATION_FILE_DIR[] = {
"idc/",
"keylayout/",
"keychars/",
};
、CONFIGURATION_FILE_EXTENSIONは各typeに対応するファイル接尾辞ですstatic const char* CONFIGURATION_FILE_EXTENSION[] = {
".idc",
".kl",
".kcm",
};
携帯電話のファイルと結びつけてみましょう.
root@lte26007:/system/usr/keylayout # ls
ls
AVRCP.kl
Generic.kl
Vendor_0079_Product_0011.kl
Vendor_045e_Product_028e.kl
Vendor_046d_Product_b501.kl
Vendor_046d_Product_c216.kl
Vendor_046d_Product_c219.kl
Vendor_046d_Product_c21d.kl
Vendor_046d_Product_c21f.kl
Vendor_046d_Product_c294.kl
Vendor_046d_Product_c299.kl
Vendor_046d_Product_c532.kl
Vendor_054c_Product_0268.kl
Vendor_0583_Product_2060.kl
Vendor_05ac_Product_0239.kl
Vendor_0b05_Product_4500.kl
Vendor_1038_Product_1412.kl
Vendor_12bd_Product_d015.kl
Vendor_1532_Product_0900.kl
Vendor_1689_Product_fd00.kl
Vendor_1689_Product_fd01.kl
Vendor_1689_Product_fe00.kl
Vendor_18d1_Product_2c40.kl
Vendor_1949_Product_0401.kl
Vendor_1bad_Product_f016.kl
Vendor_1bad_Product_f023.kl
Vendor_1bad_Product_f027.kl
Vendor_1bad_Product_f036.kl
Vendor_1d79_Product_0009.kl
Vendor_22b8_Product_093d.kl
Vendor_2378_Product_1008.kl
Vendor_2378_Product_100a.kl
comip-gpio-keys.kl
comip-keypad.kl
ft5x06.kl
h2w_headset.kl
qwerty.kl
sensor00fn11.kl
ジェネリックを見てみましょうklというファイルの切り取り部分は、スキャンコードに対応するキー値です.
.....
key 108 DPAD_DOWN
key 109 PAGE_DOWN
key 110 INSERT
key 111 FORWARD_DEL
# key 112 "KEY_MACRO"
key 113 VOLUME_MUTE
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER
key 117 NUMPAD_EQUALS
# key 118 "KEY_KPPLUSMIN
key 119 BREAK
# key 120 (undefined)
key 121 NUMPAD_COMMA
key 122 KANA
key 123 EISU
key 124 YEN
key 125 META_LEFT
key 126 META_RIGHT
key 127 MENU
key 128 MEDIA_STOP
私たちは引き続きKeyLayoutMap::loadという関数を見て、この関数は詳しく話しません.
status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
outMap->clear();
Tokenizer* tokenizer;
status_t status = Tokenizer::open(filename, &tokenizer);// Tokenizer
if (status) {
ALOGE("Error %d opening key layout map file %s.", status, filename.string());
} else {
sp<KeyLayoutMap> map = new KeyLayoutMap();
if (!map.get()) {
ALOGE("Error allocating key layout map.");
status = NO_MEMORY;
} else {
#if DEBUG_PARSER_PERFORMANCE
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
Parser parser(map.get(), tokenizer);//
status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
elapsedTime / 1000000.0);
#endif
if (!status) {
*outMap = map;
}
}
delete tokenizer;
}
return status;
}
1.2 KeyboardInputMapperのprocessKey関数
次にKeyboardInputMapper::processのprocessKey関数を見てみましょう
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
int32_t scanCode, uint32_t policyFlags) {
if (down) {
// Rotate key codes according to orientation if needed.
if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
keyCode = rotateKeyCode(keyCode, mOrientation);
}
// Add key down.
ssize_t keyDownIndex = findKeyDown(scanCode);
if (keyDownIndex >= 0) {
// key repeat, be sure to use same keycode as before in case of rotation
keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
} else {
// key down
if ((policyFlags & POLICY_FLAG_VIRTUAL)
&& mContext->shouldDropVirtualKey(when,
getDevice(), keyCode, scanCode)) {
return;
}
if (policyFlags & POLICY_FLAG_GESTURE) {
mDevice->cancelTouch(when);
}
mKeyDowns.push();
KeyDown& keyDown = mKeyDowns.editTop();
keyDown.keyCode = keyCode;
keyDown.scanCode = scanCode;
}
mDownTime = when;
} else {
// Remove key down.
ssize_t keyDownIndex = findKeyDown(scanCode);
if (keyDownIndex >= 0) {
// key up, be sure to use same keycode as before in case of rotation
keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
mKeyDowns.removeAt(size_t(keyDownIndex));
} else {
// key was not actually down
ALOGI("Dropping key up from device %s because the key was not down. "
"keyCode=%d, scanCode=%d",
getDeviceName().string(), keyCode, scanCode);
return;
}
}
int32_t oldMetaState = mMetaState;
int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
bool metaStateChanged = oldMetaState != newMetaState;
if (metaStateChanged) {
mMetaState = newMetaState;
updateLedState(false);
}
nsecs_t downTime = mDownTime;
// Key down on external an keyboard should wake the device.
// We don't do this for internal keyboards to prevent them from waking up in your pocket.
// For internal keyboards, the key layout file should specify the policy flags for
// each wake key individually.
// TODO: Use the input device configuration to control this behavior more finely.
if (down && getDevice()->isExternal()) {
policyFlags |= POLICY_FLAG_WAKE;
}
if (mParameters.handlesKeyRepeat) {
policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
}
if (metaStateChanged) {
getContext()->updateGlobalMetaState();
}
if (down && !isMetaKey(keyCode)) {
getContext()->fadePointer();
}
NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
getListener()->notifyKey(&args);
}
この関数は,最初はスキャンコードをキーボードコードに変換し,その後は主にnotifyKey関数を呼び出した.
getListener関数は次のとおりです.
InputListenerInterface* InputReader::ContextImpl::getListener() {
return mReader->mQueuedListener.get();
}
notifyKey関数は、オブジェクトを変数に配置することです.
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
mArgsQueue.push(new NotifyKeyArgs(*args));
}
二、InputReaderのloopOnce関数
最後にInputReaderのloopOnce関数に戻り、主に最後のmQueuedListener->flush関数を見ます.
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
Vector<InputDeviceInfo> inputDevices;
{ // acquire lock
AutoMutex _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
if (count) {
processEventsLocked(mEventBuffer, count);
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
// on another thread similarly waiting to acquire the InputReader lock thereby
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
mQueuedListener->flush();
}
QueuedInputListener::flush関数はファイルInputListener.cpp中.
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);
delete args;
}
mArgsQueue.clear();
}
この関数では,以前に各InputMapperに格納されていたNotifyArgsオブジェクトを巡回し,最後にNotify関数を呼び出した.
以前、私たちのNotifyArgsオブジェクトはNotifyKeyArgsオブジェクトでした.このnotifyは虚関数で、NotifyKeyArgs::notify
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyKey(this);
}
このlistenerは、QueuedInputListenerを新築したときに伝わってきました
mQueuedListener = new QueuedInputListener(listener);
ListenerはInputReaderから送られてきたので、InputReaderがInputManagerで作成されたことを知っています.
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) :
InputManagerのコンストラクション関数、転送されたInputDispatcherを見てみましょう
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
最後にこのlistenerはInputDispatcherであるため,最後に呼び出されたInputDispatcherのnotifyKey関数である.
一方,InputDispatcherのnotifyKey関数については,次のブログで分析する.