안드로이드에서는 기본 입력 장치가 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.Canvas;
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를 추가한다. 다른 변수는 상대 좌표에서 마지막 그려진 좌표 등을 저장하기 위한 변수를 정의한다.

 

        private void process() {

                ...... 

                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

+ Recent posts