1. Input Device 개요
1.1 리눅스 2.6에서 Input Device Driver 특징
커털 2.4에서 사용하던 카테고리별 입력장치를 표준화하여 하나의 인터페이스로 통일화했다.
1.2 Input Device Driver 구조
- Device Driver : 하드웨어의 입력을 처리
- Event Handler : 입력 정보를 애플리케이션에게 전달
1.3 Device Driver가 응용프로그램으로 데이터를 올리는 방법 (Event Handler)
- include/linux/input.h
struct input_event : 이 구조체를 이용해서 데이터를 응용프로그램으로 전달함
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
/* Event types */
#define EV_SYN 0x00 : event synchronize, 커널의 드라이버와 으용프로그램 간의 동기화
#define EV_KEY 0x01 : pen down과 pen up 이벤트 등이 있음
#define EV_REL 0x02 : mouse와 같은 상대좌표 특성
#define EV_ABS 0x03 : 절대값 좌표 전달 (ABS_X, ABS_Y 등)
#define EV_SW 0x05 : switch event
ex) touchscreen : EV_SYN | EV_ABS | EV_KEY
1.4 Input Device Driver에 등록된 event interface와 디바이스 정보 확인
- proc 파일시스템 이용
/proc/bus/input/devices : 현재 시스템에 연결되어 있는 디바이스.
/proc/bus/input/devices
I : Information, 디바이스의 정보 중 연결된 버스 번호, 생산자 ID, 제품 ID, 버전을 보여준다.
N : Name, 디바이스 이름을 알려준다. (안드로이드에서 키보드 포팅할 때 중요한 역할을 한다.)
P : Physical, 내부적으로 표현되는 물리적인 장치 이름을 가리킨다.
H : Handler, 이 device driver와 연결되어 동작하는 디바이스 파일 노드가 어떤 것인지를 표현한다.
S : System File, /sys 디렉토리의 어느 것을 사용하는지에 대한 값을 얻을 수 있다.
B : Bit Mask Info, evbit와 keybit와 같이 장치의 타입을 알 수 있다.
/dev/input/* : event interface 장치
응용프로그램에서 event 디바이스 파일을 열어서 디바이스의 특성을 ioctl로 파악한 후, 적절한 입력장치로부터 데이터를 입력받는다.
1.5 Input Device가 적절하게 데이터를 전달하는지 확인하는 코드 aesopev.c
- key input test : ./aesopev key
- touch test : ./aesopev touch
2. 안드로이드 Input Device Driver
2.1 안드로이드 Input Device 예
- keyboard : /dev/input/event0
- Touchscreen : /dev/input/event1
- Switch driver : /dev/input/event2
2.2 안드로이드 Key Driver
2.2.1 안드로이드는 커널에서 올라오는 입력장치를 EventHub에서 처리하고, 안드로이드의 key layout file과 비교한 후, 애플리케이션 Framework 쪽으로 해당 키 값을 올려준다.
-키보드의 키에 할당되어 있는 key scancode 값 확인
- include/linux/input.h
#define KEY_BOOKMARKS 156 /* AC Bookmarks */
#define KEY_COMPUTER 157
#define KEY_BACK 158 /* AC Back */
#define KEY_FORWARD 159 /* AC Forward */
- drivers/input/keyboard/s3c-keypad.c
- 안드로이드로 올라가는 keycode 값 확인
- 안드로이드 Rootfs/system/usr/keylayout/qwerty.kl
// key | scancode | keycode | flags
key 158 BACK WAKE_DROPPED
key 230 SOFT_RIGHT WAKE
key 60 SOFT_RIGHT WAKE
key 107 ENDCALL WAKE_DROPPED
key 62 ENDCALL WAKE_DROPPED
key 229 MENU WAKE_DROPPED
key 139 MENU WAKE_DROPPED
key 59 MENU WAKE_DROPPED
key 127 SEARCH WAKE_DROPPED
key 217 SEARCH WAKE_DROPPED
key 228 POUND
key 227 STAR
key 231 CALL WAKE_DROPPED
key 61 CALL WAKE_DROPPED
key 232 DPAD_CENTER WAKE_DROPPED
key 108 DPAD_DOWN WAKE_DROPPED
key 103 DPAD_UP WAKE_DROPPED
key 102 HOME WAKE
key 105 DPAD_LEFT WAKE_DROPPED
key 106 DPAD_RIGHT WAKE_DROPPED
key 115 VOLUME_UP WAKE
key 114 VOLUME_DOWN WAKE
key 116 POWER WAKE
key 212 CAMERA
- keycode와 flag 선언 : 안드로이드/frameworks/base/include/ui/KeycodeLabels.h
- KeyLatoutMap 클래스 : 안드로이드/frameworks/base/libs/ui/KeyLayoutMap.h와 KeyLayoutMap.cpp에 정의됨.
2.3 안드로이드 Touchscreen Driver
2.3.1 Touch Panel Device Driver 동작 절차
- X-Window : Touch Device > ADC > Device Driver > TS Lib > X-Windows
- 안드로이드 : Touch Device > ADC > Device Driver > Android
안드로이드는 device driver에서 안드로이드 시스템으로 직접 리포팅하여 터치스크린 입력을 처리한다.
2.4 안드로이드 Switch Event Device Driver 등록 방법
Input Device Driver 중 EV_SW(Switch Event) 타입을 갖는 device driver로 작성해야 함.
- include/linux/input.h
/*
* Switch events
*/
#define SW_LID 0x00 /* set = lid shut */
#define SW_TABLET_MODE 0x01 /* set = tablet mode */
#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */
#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any"
set = radio enabled */
#define SW_RADIO SW_RFKILL_ALL /* deprecated */
#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */
#define SW_DOCK 0x05 /* set = plugged into dock */
#define SW_LINEOUT_INSERT 0x06 /* set = inserted */
#define SW_JACK_PHYSICAL_INSERT 0x07 /* set = mechanical switch set */
#define SW_VIDEOOUT_INSERT 0x08 /* set = inserted */
#define SW_CAMERA_LENS_COVER 0x09 /* set = lens covered */
#define SW_KEYPAD_SLIDE 0x0a /* set = keypad slide out */
#define SW_FRONT_PROXIMITY 0x0b /* set = front proximity sensor active */
#define SW_MAX 0x0f
#define SW_CNT (SW_MAX+1)
/*
- SW_LID 이벤트가 발생할 수 있도록 적절하게 코드를 작성해야 한다.
interrupt 처리 함수
static irqreturn_t sw_lid_keyevent(int irq, void *dev_id)
{
int switch_open_detected = 0;
switch_open_detected = read_gpio_lid();
if(switch_open_detected)
{
input_report_switch(torbreck_switchbut_private_t->input, SW_LID, 1);
}
else
{
input_report_switch(torbreck_switchbut_private_t->input, SW_LID,0);
}
return IRQ_HANDLED;
}
...
input device driver 등록 함수
중요 코드
seb_bit(EV_SW, input_dev->swbit);
input_set_capability(input_dev, EV_SW, SW_LID);
- 최종적으로 PhoneWindowManager.java로 올라가 필요 동작을 하도록 코드를 작성한다.
3. 안드로이드 Input Manager
3.1 안드로이드 Input Device 처리 구조
Kernel > EventHub > NativeInputManager - JNI > InputManager > WindowManager > PhoneWindowManager
① frameworks/base/services/java/com/android/server/WindowManagerService.java - Framework
② frameworks/base/services/java/com/android/server/InputManager.java - Framework
③ frameworks/base/services/jni/com_android_server_InputManager.cpp - JNI
④ frameworks/base/libs/ui/InputManager.cpp - Native
- InputManager ~ Native 함수까지 시작 호출 과정정
① WindowManagerService.jave
private WindowManagerService(Context context, PowerManagerService pm,
boolean haveInputMethods) {
mContext = context;
mHaveInputMethods = haveInputMethods;
mLimitedAlphaCompositing = context.getResources().getBoolean(
com.android.internal.R.bool.config_sf_limitedAlpha);
mPowerManager = pm;
mPowerManager.setPolicy(mPolicy);
PowerManager pmc = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mScreenFrozenLock = pmc.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"SCREEN_FROZEN");
mScreenFrozenLock.setReferenceCounted(false);
mActivityManager = ActivityManagerNative.getDefault();
mBatteryStats = BatteryStatsService.getService();
// Get persisted window scale setting
mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(),
Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(),
Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
// Track changes to DevicePolicyManager state so we can enable/disable keyguard.
IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter);
mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
"KEEP_SCREEN_ON_FLAG");
mHoldingScreenWakeLock.setReferenceCounted(false);
mInputManager = new InputManager(context, this);
PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
thr.start();
synchronized (thr) {
while (!thr.mRunning) {
try {
thr.wait();
} catch (InterruptedException e) {
}
}
}
mInputManager.start();
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
}
② InputManager.java
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart();
}
③ com_android_server_InputManager.cpp
static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
if (checkInputManagerUnitialized(env)) {
return;
}
status_t result = gNativeInputManager->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
④ InputManager.cpp
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
LOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
LOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
3.3 InputReader 클래스와 InputDispatcher 클래스
3.3.1 InputReader 클래스 처리 순서
Input Device에 의해 들어온 데이터를 읽어와서 처리하는 클래스다.
1) InputManger.cpp - start()에서 run()함수 호출
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
LOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
LOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
2) InputReader.cpp - InputReaderThread에서 loopOnce()함수 수행
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
3) InputReader.cpp - loopOnce에서 mEventHub 객체를 통해 getEvent() 함수를 호출하여 이벤트 데이터를 가져오고 process() 함수를 호출한다.
void InputReader::loopOnce() {
RawEvent rawEvent;
mEventHub->getEvent(& rawEvent);
#if DEBUG_RAW_EVENTS
LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",
rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
rawEvent.value);
#endif
process(& rawEvent);
}
4) InputReader.cpp - process - prcoess함수는 EventHub를 통해 받아온 이벤트를 처리한다. 처리되는 이벤트는 4종류로 분류된다. ( DEVICE_ADDED, DEVICE_REMOVED, FINISHED_DEVICE_SCAN, 기타)
void InputReader::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDevice(rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
removeDevice(rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChanged(rawEvent->when);
break;
default:
consumeEvent(rawEvent);
break;
}
}
① DEVICE_ADDED - addDevice()함수 - createDevice()를 호출하여 InputDevice 클래스를 생성하고 InputDevice의 configure() 함수를 호출함
void InputReader::addDevice(int32_t deviceId) {
String8 name = mEventHub->getDeviceName(deviceId);
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
InputDevice* device = createDevice(deviceId, name, classes);
device->configure();
if (device->isIgnored()) {
LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", deviceId, name.string());
} else {
LOGI("Device added: id=0x%x, name=%s, sources=%08x", deviceId, name.string(),
device->getSources());
}
bool added = false;
{ // acquire device registry writer lock
RWLock::AutoWLock _wl(mDeviceRegistryLock);
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
mDevices.add(deviceId, device);
added = true;
}
} // release device registry writer lock
if (! added) {
LOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
delete device;
return;
}
}
② DEVICE_REMOVED - removeDevice() - 인자로 넘어온 deviceId를 가지고 deviceIndex를 구해서 해당 InputDevice 클래스를 삭제한다.
void InputReader::removeDevice(int32_t deviceId) {
bool removed = false;
InputDevice* device = NULL;
{ // acquire device registry writer lock
RWLock::AutoWLock _wl(mDeviceRegistryLock);
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex >= 0) {
device = mDevices.valueAt(deviceIndex);
mDevices.removeItemsAt(deviceIndex, 1);
removed = true;
}
} // release device registry writer lock
if (! removed) {
LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
return;
}
if (device->isIgnored()) {
LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
device->getId(), device->getName().string());
} else {
LOGI("Device removed: id=0x%x, name=%s, sources=%08x",
device->getId(), device->getName().string(), device->getSources());
}
device->reset();
delete device;
}
③ FINISHED_DEVICE_SCAN - handleConfigurationChanged() - InputDevice의 configuration을 업데이트한다.
void InputReader::handleConfigurationChanged(nsecs_t when) {
// Reset global meta state because it depends on the list of all configured devices.
updateGlobalMetaState();
// Update input configuration.
updateInputConfiguration();
// Enqueue configuration changed.
mDispatcher->notifyConfigurationChanged(when);
}
④ 나머지 - consumeEvent() - deviceId로 deviceIndex를 찾아서 InputDevice 클래스 정보를 가지고 온다. 그 다음 process() 함수를 호출한다.
void InputReader::consumeEvent(const RawEvent* rawEvent) {
int32_t deviceId = rawEvent->deviceId;
{ // acquire device registry reader lock
RWLock::AutoRLock _rl(mDeviceRegistryLock);
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
LOGW("Discarding event for unknown deviceId %d.", deviceId);
return;
}
InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()) {
//LOGD("Discarding event for ignored deviceId %d.", deviceId);
return;
}
device->process(rawEvent);
} // release device registry reader lock
}
InputReader.cpp - InputDevice::process()
void InputDevice::process(const RawEvent* rawEvent) {
size_t numMappers = mMappers.size();
for (size_t i = 0; i < numMappers; i++) {
InputMapper* mapper = mMappers[i];
mapper->process(rawEvent);
}
}
InputReader.cpp - KeyboardInputMapper::process() - key 이벤트인 경우만 처리되고, processKey()함수를 호출한다.
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {
int32_t scanCode = rawEvent->scanCode;
if (isKeyboardOrGamepadKey(scanCode)) {
processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
rawEvent->flags);
}
break;
}
}
}
InputReader.cpp - KeyboardInputMapper::processKey() - key 이벤트를 처리하고 InputDispatcher의 notifyKey()함수를 호출한다.
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
int32_t scanCode, uint32_t policyFlags) {
int32_t newMetaState;
nsecs_t downTime;
bool metaStateChanged = false;
{ // acquire lock
AutoMutex _l(mLock);
if (down) {
// Rotate key codes according to orientation if needed.
// Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
if (mAssociatedDisplayId >= 0) {
int32_t orientation;
if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) {
return;
}
keyCode = rotateKeyCode(keyCode, orientation);
}
// Add key down.
ssize_t keyDownIndex = findKeyDownLocked(scanCode);
if (keyDownIndex >= 0) {
// key repeat, be sure to use same keycode as before in case of rotation
keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
} else {
// key down
mLocked.keyDowns.push();
KeyDown& keyDown = mLocked.keyDowns.editTop();
keyDown.keyCode = keyCode;
keyDown.scanCode = scanCode;
}
mLocked.downTime = when;
} else {
// Remove key down.
ssize_t keyDownIndex = findKeyDownLocked(scanCode);
if (keyDownIndex >= 0) {
// key up, be sure to use same keycode as before in case of rotation
keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
mLocked.keyDowns.removeAt(size_t(keyDownIndex));
} else {
// key was not actually down
LOGI("Dropping key up from device %s because the key was not down. "
"keyCode=%d, scanCode=%d",
getDeviceName().string(), keyCode, scanCode);
return;
}
}
int32_t oldMetaState = mLocked.metaState;
newMetaState = updateMetaState(keyCode, down, oldMetaState);
if (oldMetaState != newMetaState) {
mLocked.metaState = newMetaState;
metaStateChanged = true;
}
downTime = mLocked.downTime;
} // release lock
if (metaStateChanged) {
getContext()->updateGlobalMetaState();
}
getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
}
3.3.2 InputDispatcher 클래스 처리 순서
InputReader 클래스에서 처리된 이벤트 데이터를 저장하고 처리한다.
1) InputDispatcher.cpp - notifyKey() - 발생한 이벤트가 유효한지 validateKeyEvent()로 확인한 다음 - KeyEntry를 생성하고 - enqueueInboundEventLocked() 함수를 호출한다.
void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
uint32_t policyFlags, int32_t action, int32_t flags,
int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
#if DEBUG_INBOUND_EVENT_DETAILS
LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
"flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
eventTime, deviceId, source, policyFlags, action, flags,
keyCode, scanCode, metaState, downTime);
#endif
if (! validateKeyEvent(action)) {
return;
}
policyFlags |= POLICY_FLAG_TRUSTED;
mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,
keyCode, scanCode, /*byref*/ policyFlags);
bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
int32_t repeatCount = 0;
KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
deviceId, source, policyFlags, action, flags, keyCode, scanCode,
metaState, repeatCount, downTime);
needWake = enqueueInboundEventLocked(newEntry);
} // release lock
if (needWake) {
mLooper->wake();
}
}