안드로이드에서는 기본 입력 장치가 Touch이다. 실제로는 몇가지 입력 장치를 위한 준비가 되어 있는 것으로 보이며 인터넷에서 마우스를 위한 패치된 코드가 있다.
http://gitorious.org/0xdroid/frameworks_base/commit/a236aff3fcfca4ae3e200de48bfbb76daf2b7bb9
코드 패치 하기 전에 aesop에서 안드로이드의 key/touch event dispatch 과정을 정리한 내용이 있는데 이를 참고하면 좀 더 이해가 쉬울 것 같다.
실제 패치하여 동작시키면 완벽한 것은 아니고 몇가지 문제점을 가지고 있다.
일단 수정된 사항 위주로 정리해 본다.
/frameworks/base/core/java/android/view/RawInputEvent.java
public class RawInputEvent {
// Event class as defined by EventHub.
public static final int CLASS_KEYBOARD = 0x00000001;
public static final int CLASS_ALPHAKEY = 0x00000002;
public static final int CLASS_TOUCHSCREEN = 0x00000004;
public static final int CLASS_TRACKBALL = 0x00000008;
public static final int CLASS_MOUSE= 0x00000010;
// More special classes for QueuedEvent below.
public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;
......
}
코드를 보면 알곘지만 keyboard, alphakey, touchscreen, trackball 을 기본 지원 장치로 준비해 두었다. 이 패치는 trackball의 내용을 수정하여 mouse에 대응하도록 작업되었다.
/frameworks/base/include/ui/EventHub.h
class EventHub : public RefBase
{
public:
EventHub();
status_t errorCheck() const;
// bit fields for classes of devices.
enum {
CLASS_KEYBOARD = 0x00000001,
CLASS_ALPHAKEY = 0x00000002,
CLASS_TOUCHSCREEN = 0x00000004,
CLASS_TRACKBALL = 0x00000008,
CLASS_MOUSE = 0x00000010
};
......
}
java 소스와 c 소스를 모두 고쳐주어야 하기 때문에 위와 같이 두 곳에서 타입을 추가해준다.
/frameworks/base/libs/ui/EventHub.cpp
int EventHub::open_device(const char *deviceName)
{
......
if (test_bit(BTN_MOUSE, key_bitmask)) {
uint8_t rel_bitmask[(REL_MAX+1)/8];
memset(rel_bitmask, 0, sizeof(rel_bitmask));
LOGV("Getting relative controllers...");
if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0)
{
if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) {
if (test_bit(BTN_LEFT,key_bitmask) &&
test_bit(BTN_RIGHT,key_bitmask))
device->classes |= CLASS_MOUSE;
else
device->classes |= CLASS_TRACKBALL;
}
}
}
}
touchscreen의 경우 디바이스 드라이버로부터 전달되는 좌표 정보는 절대 좌표이지만 mouse나 trackball 의 경우는 상대적인 좌표 이동 정보를 받게 된다. mouse나 trackball의 좌표 처리를 REL_X와 REL_Y 조건을 비교하면 알 수 있는데 mouse와 trackball을 구분하기 위해서 BTN_LEFT, BTN_RIGHT의 유무도 체크하고 있다. (개인적인 생각으로 TRACK BALL도 추후에는 두 경우가 있을 것 같은데 구분하는 이유를 모르겠다.)
/frameworks/base/services/java/com/android/server/KeyInputQueue.java
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
// Is it a key event?
if (type == RawInputEvent.EV_KEY &&
(classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
(scancode < RawInputEvent.BTN_FIRST ||
scancode > RawInputEvent.BTN_LAST)) {
boolean down;
if (ev.value != 0) {
down = true;
di.mDownTime = curTime;
} else {
down = false;
}
int keycode = rotateKeyCodeLocked(ev.keycode);
addLocked(di, curTime, ev.flags,
RawInputEvent.CLASS_KEYBOARD,
newKeyEvent(di, di.mDownTime, curTime, down,
keycode, 0, scancode,
((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
? KeyEvent.FLAG_WOKE_HERE : 0));
} else if (ev.type == RawInputEvent.EV_KEY) {
if (ev.scancode == RawInputEvent.BTN_TOUCH &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
di.mAbs.changed = true;
di.mAbs.down = ev.value != 0;
}
if (ev.scancode == RawInputEvent.BTN_MOUSE &&
(((classes&RawInputEvent.CLASS_TRACKBALL) != 0) ||
((classes&RawInputEvent.CLASS_MOUSE) != 0))) {
di.mRel.changed = true;
di.mRel.down = ev.value != 0;
// send = true;
}
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
if (ev.scancode == RawInputEvent.ABS_X) {
di.mAbs.changed = true;
di.mAbs.x = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_Y) {
di.mAbs.changed = true;
di.mAbs.y = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_PRESSURE) {
di.mAbs.changed = true;
di.mAbs.pressure = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) {
di.mAbs.changed = true;
di.mAbs.size = ev.value;
}
} else if (ev.type == RawInputEvent.EV_REL &&
(((classes&RawInputEvent.CLASS_TRACKBALL) != 0) ||
((classes&RawInputEvent.CLASS_MOUSE) != 0))) {
// Add this relative movement into our totals.
if (ev.scancode == RawInputEvent.REL_X) {
di.mRel.changed = true;
di.mRel.x += ev.value;
} else if (ev.scancode == RawInputEvent.REL_Y) {
di.mRel.changed = true;
di.mRel.y += ev.value;
}
}
if (send || ev.type == RawInputEvent.EV_SYN) {
if (mDisplay != null) {
if (!mHaveGlobalMetaState) {
computeGlobalMetaStateLocked();
}
MotionEvent me;
me = di.mAbs.generateMotion(di, curTime, true,
mDisplay, mOrientation, mGlobalMetaState);
if (false)
Log.v(TAG, "Absolute: x=" + di.mAbs.x
+ " y=" + di.mAbs.y + " ev=" + me);
if (me != null) {
if (WindowManagerPolicy.WATCH_POINTER) {
Log.i(TAG, "Enqueueing: " + me);
}
addLocked(di, curTime, ev.flags,
RawInputEvent.CLASS_TOUCHSCREEN, me);
}
me = di.mRel.generateMotion(di, curTime, false,
mDisplay, mOrientation, mGlobalMetaState);
if (false)
Log.v(TAG, "Relative: x=" + di.mRel.x
+ " y=" + di.mRel.y + " ev=" + me);
if (me != null) {
if ((classes & RawInputEvent.CLASS_TRACKBALL) != 0) {
addLocked(di, curTime, ev.flags,
RawInputEvent.CLASS_TRACKBALL, me);
} else {
addLocked(di, curTime, ev.flags,
RawInputEvent.CLASS_MOUSE, me);
}
}
}
}
}
};
Thread를 수정해서 mouse 입력 처리를 할 수 있도록 한다. 크게 수정해야 될 곳은 3곳이다. 처음 두곳은 CLASS_TRACKBALL에 의해서만 relative evnet가 발생하는 것을 CLASS_MOUSE로도 가능하게 한다. 마지막은 원래는 relative event를 발생시키는 것이 CLASS_TRACKBALL 뿐이지만 이제는 CLASS_MOUSE도 발생시키므로 이를 구분해서 addLocked()을 호출하기 위한 코드이다.
/frameworks/base/services/java/com/android/server/WindowManagerService.java
마지막 수정이 실제 화면에 보여주기 위한 코드이다. 안드로이드의 경우 CLASS_TRACKBALL을 정의해서 mouse나 trackball에 대한 입력을 처리할 수 있도록은 해 두었는데 실제 화면상에 mouse cursor를 그리는 부분은 작업이 되어 있지 않다. 이를 처리하는 내용이 주된 내용이다.
import! android.graphics.Path;
화면상에 mouse cursor를 그려주기 위해서 Canvas와 Path를 import! 한다.
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor {
SurfaceSession mFxSession;
private DimAnimator mDimAnimator = null;
Surface mBlurSurface;
boolean mBlurShown;
Surface mMouseSurface;
int mShowMouse = 0;
int mMlx;
int mMly;
int mMlw;
int mMlh;
......
}
mouse cursor는 새로운 surface에 그려주고 이를 surface flinger를 이용해서 합쳐주어야 하기 때문에 mMouseSurface를 추가한다. 다른 변수는 상대 좌표에서 마지막 그려진 좌표 등을 저장하기 위한 변수를 정의한다.
......
try {
if (ev != null) {
curTime = ev.when;
int eventType;
if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {
eventType = eventType((MotionEvent)ev.event);
} else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
ev.classType == RawInputEvent.CLASS_TRACKBALL ||
ev.classType == RawInputEvent.CLASS_MOUSE) {
eventType = LocalPowerManager.BUTTON_EVENT;
} else {
eventType = LocalPowerManager.OTHER_EVENT;
}
try {
long now = SystemClock.uptimeMillis();
if ((now - mLastBatteryStatsCallTime)
>= MIN_TIME_BETWEEN_USERACTIVITIES) {
mLastBatteryStatsCallTime = now;
mBatteryStats.noteInputEvent();
}
} catch (RemoteException e) {
// Ignore
}
mPowerManager.userActivity(curTime, false, eventType, false);
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
KeyEvent ke = (KeyEvent)ev.event;
if (ke.isDown()) {
lastKey = ke;
keyRepeatCount = 0;
lastKeyTime = curTime;
nextKeyTime = lastKeyTime
+ KEY_REPEAT_FIRST_DELAY;
if (DEBUG_INPUT) Log.v(
TAG, "Received key down: first repeat @ "
+ nextKeyTime);
} else {
lastKey = null;
// Arbitrary long timeout.
lastKeyTime = curTime;
nextKeyTime = curTime + LONG_WAIT;
if (DEBUG_INPUT) Log.v(
TAG, "Received key up: ignore repeat @ "
+ nextKeyTime);
}
dispatchKey((KeyEvent)ev.event, 0, 0);
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
//Log.i(TAG, "Read next event " + ev);
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_MOUSE:
MotionEvent mmev = (MotionEvent)ev.event;
int mcx = mMlx + (int)(mmev.getX()* mmev.getXPrecision());
int mcy = mMly + (int)(mmev.getY()* mmev.getYPrecision());
mcx = ((mcx < 0) ? 0 :(mcx >= mDisplay.getWidth() ?(mDisplay.getWidth()-1):mcx));
mcy = ((mcy < 0) ? 0 :(mcy >= mDisplay.getHeight()?(mDisplay.getHeight() - 1):mcy));
mmev.setLocation((float) mcx, (float) mcy);
dispatchPointer(ev, mmev, 0, 0);
if (mMouseSurface != null && (mMlx != mcx || mMly != mcy)) {
// Should we use lock? synchronized(mWindowMap) {
Surface.openTransaction();
if (DEBUG_INPUT)
Log.i(TAG,
"Open transaction for the mouse surface");
WindowState top =
(WindowState)mWindows.get(mWindows.size() - 1);
try {
if (DEBUG_INPUT)
Log.i(TAG, "Move surf, x: " +
Integer.toString(mcx) + " y:"
+ Integer.toString(mcy));
mMouseSurface.setPosition(mcx,mcy);
mMouseSurface.setLayer(top.mAnimLayer + 1);
if (mShowMouse != 1) {
mMouseSurface.show();
mShowMouse = 1;
}
mMlx = mcx;
mMly = mcy;
} catch ( RuntimeException e) {
Log.e(TAG, "Failure showing mouse surface",e);
}
Surface.closeTransaction();
}
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);
break;
}
}
}
}
첫번째 수정은 위에서와 마찬가지로 CLASS_TRACKBALL로만 되어 있는 것에 CLASS_MOUSE에 대한 코드를 추가한 것이다.
두번째 위치는 mouse event 정보로 올라온 값으로부터 mouse가 그려져야 할 위치를 계산하는 코드이다.
setPosition으로 mMouseSurface의 위치를 설정하고 show()를 호출하여 mouse cursor를 보여준다. 한번 show되면 두번 호출하지 않기 위하여 mShowMouse를 이용해서 flag 관리를 한다.
private final void performLayoutAndPlaceSurfacesLockedInner(
boolean recoveringMemory) {
final long currentTime = SystemClock.uptimeMillis();
final int dw = mDisplay.getWidth();
final int dh = mDisplay.getHeight();
final int N = mWindows.size();
int i;
// FIRST LOOP: Perform a layout, if needed.
performLayoutLockedInner();
if (mFxSession == null) {
mFxSession = new SurfaceSession();
}
if (mMouseSurface == null) {
int mMx, mMy, mMw, mMh;
Canvas mCanvas;
Path mPath = new Path();
if (DEBUG_INPUT)
Log.i(TAG, "Create Mouse Surface");
mMw = 20;
mMh = 20;
mMx = (mDisplay.getWidth() - mMw) / 2;
mMy = (mDisplay.getHeight() - mMh) / 2;
try {
/*
*First Mouse event, create Surface
*/
mMouseSurface =
new Surface(mFxSession,
0,-1,mMw,mMy,
PixelFormat.TRANSPARENT,
Surface.FX_SURFACE_NORMAL);
mCanvas = mMouseSurface.lockCanvas(null);
mCanvas.drawColor(0x0);
mPath.moveTo!(0.0f,0.0f);
mPath.lineTo(16.0f, 0.0f);
mPath.lineTo(0.0f, 16.0f);
mPath.close();
mCanvas.clipPath(mPath);
mCanvas.drawColor(0x66666666);
mMouseSurface.unlockCanvasAndPost(mCanvas);
mMouseSurface.openTransaction();
mMouseSurface.setSize(mMw,mMh);
mMouseSurface.closeTransaction();
} catch (Exception e) {
Log.e(TAG, "Exception creating mouse surface",e);
}
mMlx = mMx;
mMly = mMy;
mMlw = mMw;
mMlh = mMh;
}
......
}
여기에서는 mMouseSurface를 생성해 주고, mMouseSurface에서 mCanvas와 mPath를 이용해서 mouse cursor를 그려준다. 실제로 여기에서는 mMouseSurface에 그림 그려주는 것과 좌표를 초기화 해주는 것 외에는 특별한 작업은 하지 않는다. mouse 입력에 따른 동작은 이전 코드에서 처리된다.
출처 : http://blog.daum.net/_blog/BlogTypeView.do?blogid=0CNH3&articleno=16011039#ajax_history_home
'안드로이드' 카테고리의 다른 글
Android Emulator의 성능을 빠르게 하기 (0) | 2011.01.27 |
---|---|
ANR 발생 원인 (0) | 2011.01.27 |
Activity Lifecycle - 2[필독] (0) | 2011.01.27 |
Telephony & SMS & Search & Play Media & MMS & Web Intent (0) | 2011.01.27 |
Device를 Rotate시 꼭 알아두어야 할사항. (0) | 2011.01.27 |