第一章 摘要

在Linux 内核支持的基础上, Android 在其 2.0 源码中加入多点触摸功能。由此触摸屏在 Android 的 frameworks 被完全分为 2 种实现途径:单点触摸屏的单点方式,多点触摸屏的单点和多点方式。


第二章 软件位

在Linux 的 input.h 中,多点触摸功能依赖于以下几个主要的软件位:

……………………… .. 
#define SYN_REPORT     0 
#define SYN_CONFIG     1 
#define SYN_MT_REPORT     2 
……………………… ... 
#define ABS_MT_TOUCH_MAJOR   0x30   /* Major axis of touching ellipse */ 
#define ABS_MT_TOUCH_MINOR   0x31   /* Minor axis (omit if circular) */ 
#define ABS_MT_WIDTH_MAJOR   0x32   /* Major axis of approaching ellipse */ 
#define ABS_MT_WIDTH_MINOR   0x33   /* Minor axis (omit if circular) */ 
#define ABS_MT_ORIENTATION   0x34   /* Ellipse orientation */ 
#define ABS_MT_POSITION_X   0x35   /* Center X ellipse position */ 
#define ABS_MT_POSITION_Y   0x36   /* Center Y ellipse position */ 
#define ABS_MT_TOOL_TYPE   0x37   /* Type of touching device */ 
#define ABS_MT_BLOB_ID     0x38   /* Group a set of packets as a blob */ 
…………………………

在Android 中对应的软件位定义在 RawInputEvent.java 中 :

………………… .. 
public class RawInputEvent { 
……………… . 
   public static final int CLASS_TOUCHSCREEN_MT = 0x00000010; 
……………… .. 
   public static final int ABS_MT_TOUCH_MAJOR = 0x30; 
  public static final int ABS_MT_TOUCH_MINOR = 0x31; 
  public static final int ABS_MT_WIDTH_MAJOR = 0x32; 
  public static final int ABS_MT_WIDTH_MINOR = 0x33; 
  public static final int ABS_MT_ORIENTATION = 0x34; 
  public static final int ABS_MT_POSITION_X = 0x35; 
  public static final int ABS_MT_POSITION_Y = 0x36; 
  public static final int ABS_MT_TOOL_TYPE = 0x37; 
  public static final int ABS_MT_BLOB_ID = 0x38; 
………………… . 
public static final int SYN_REPORT = 0; 
  public static final int SYN_CONFIG = 1; 
public static final int SYN_MT_REPORT = 2; 
……………… ..


在Android 中,多点触摸的实现方法在具体的代码实现中和单点是完全区分开的。在 Android 代码的 EventHub.cpp 中,单点屏和多点屏由如下代码段来判定:

int EventHub::open_device(const char *deviceName) 
{ 
……………………… 
if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask) 
      && test_bit(ABS_MT_POSITION_X, abs_bitmask) 
      && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) { 
    device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;   
//     LOGI("It is a multi-touch screen!"); 
  }  
  //single-touch? 
  else if (test_bit(BTN_TOUCH, key_bitmask) 
      && test_bit(ABS_X, abs_bitmask)  
      && test_bit(ABS_Y, abs_bitmask)) { 
    device->classes |= CLASS_TOUCHSCREEN; 
//     LOGI("It is a single-touch screen!"); 
  } 
……………… .. 
}

我们知道,在触摸屏驱动中,通常在probe 函数中会调用 input_set_abs_params 给设备的input_dev 结构体初始化,这些 input_dev 的参数会在 Android 的 EventHub.cpp 中被读取。如上可知,如果我们的触摸屏想被当成多点屏被处理,只需要在驱动中给 input_dev 额外增加以下几个参数即可:

input_set_abs_params(mcs_data.input, ABS_MT_POSITION_X,   pdata->abs_x_min,  pdata->abs_x_max,   0, 0); 
input_set_abs_params(mcs_data.input, ABS_MT_POSITION_Y,   pdata->abs_y_min,  pdata->abs_y_max,   0, 0); 
input_set_abs_params(mcs_data.input, ABS_MT_TOUCH_MAJOR, 0, 15,   0, 0); 
相当于单点屏的 ABX_PRESSURE 
input_set_abs_params(mcs_data.input, ABS_MT_WIDTH_MAJOR, 0, 15,   0, 0);   
//相当于单点屏的 ABS_TOOL_WIDTH


注:

为了让我们的驱动代码支持所有的Android 版本,无论是多点屏还是单点屏,一般都会保留单点屏的事件,如 ABS_TOUCH, ABS_PRESSURE, ABS_X, ABS_Y 等。另外,由于在 Android2.0 前支持多点的 frameworks 大多是用 HAT0X,HAT0Y 来实现的,所以一般也会上报这 2 个事件。



第三章 同步方式

由于多点触摸技术需要采集到多个点,然后再一起处理这些点,所以在软件实现中需要保证每一波点的准确性和完整性。因此,Linux 内核提供了 input_mt_sync(struct input_dev * input) 函数。在每波的每个点上报后需要紧跟一句 input_mt_sync(),  当这波所有点上报后再使用 input_sync() 进行同步。例如一波要上报 3 个点:

/* 上报点 1*/ 
…………… .. 
input_mt_sync(input); 
/* 上报点 2*/ 
…………… .. 
input_mt_sync(input); 
/* 上报点 3*/ 
…………… .. 
input_mt_sync(input); 
input_sync(input);

注:即使是仅上报一个点的单点事件,也需要一次input_my_sync 。


在Android 的 KeyInputQueue.java 中,系统创建了一个线程,然后把所有的 Input 事件放入一个队列:

public abstract class KeyInputQueue { 
…………………… 
Thread mThread = new Thread("InputDeviceReader") { 
        public void run() { 
            android.os.Process.setThreadPriority( 
                    android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); 
             
            try { 
                RawInputEvent ev = new RawInputEvent(); 
                while (true) { 
                      InputDevice di; 
                       // block, doesn't release the monitor 
                       readEvent(ev); 
if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { 
                        synchronized (mFirst) { 
                            di = newInputDevice(ev.deviceId); 
                            mDevices.put(ev.deviceId, di); 
                            configChanged = true; 
                        } 
                    } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { 
                        synchronized (mFirst) { 
                            Log.i(TAG, "Device removed: id=0x" 
                                    + Integer.toHexString(ev.deviceId)); 
                            di = mDevices.get(ev.deviceId); 
                            if (di != null) { 
                                mDevices.delete(ev.deviceId); 
                                configChanged = true; 
                            } else { 
                                Log.w(TAG, "Bad device id: " + ev.deviceId); 
                            } 
                        } 
                    } else { 
                        di = getInputDevice(ev.deviceId); 
                         
                        // first crack at it 
                        send = preprocessEvent(di, ev); 

                        if (ev.type == RawInputEvent.EV_KEY) { 
                            di.mMetaKeysState = makeMetaState(ev.keycode, 
                                    ev.value != 0, di.mMetaKeysState); 
                            mHaveGlobalMetaState = false; 
                        } 
                    } 

                    if (di == null) { 
                        continue; 
                    } 
                     
                    if (configChanged) { 
                        synchronized (mFirst) { 
                            addLocked(di, SystemClock.uptimeMillis(), 0, 
                                    RawInputEvent.CLASS_CONFIGURATION_CHANGED, 
                                    null); 
                        } 
                    } 
                     
                    if (!send) { 
                        continue; 
                    } 
                     
                    synchronized (mFirst) { 
                       ……………………… . 
                     if (type == RawInputEvent.EV_KEY && 
                                (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && 
                                (scancode < RawInputEvent.BTN_FIRST || 
                                        scancode > RawInputEvent.BTN_LAST)) { 
键盘按键事件  */ 
                       …………………… . 
                      } else if (ev.type == RawInputEvent.EV_KEY) { 
下面是 EV_KEY 事件分支,只支持单点的触摸屏有按键事件, 
而支持多点的触摸屏没有按键事件,只有绝对坐标事件 
*/ 
                           if (ev.scancode == RawInputEvent.BTN_TOUCH && 
                                    (classes&(RawInputEvent.CLASS_TOUCHSCREEN 
                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT)) 
                                            == RawInputEvent.CLASS_TOUCHSCREEN) { 
只支持单点的触摸屏的按键事件  */ 
                        ………………………………… 
                             } else if (ev.scancode == RawInputEvent.BTN_MOUSE && 
                                    (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { 
鼠标和轨迹球  */ 
                        ……………………… . 
                       } else if (ev.type == RawInputEvent.EV_ABS && 
                                (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { 
下面才是多点触摸屏上报的事件  */ 
                          if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { 
                                di.mAbs.changed = true; 
                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 
                                        + MotionEvent.SAMPLE_PRESSURE] = ev.value; 
                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { 
                                di.mAbs.changed = true; 
                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 
                                    + MotionEvent.SAMPLE_X] = ev.value;                           
                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { 
                                di.mAbs.changed = true; 
                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 
                                    + MotionEvent.SAMPLE_Y] = ev.value;                             
                            } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { 
                                di.mAbs.changed = true; 
                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 
                                    + MotionEvent.SAMPLE_SIZE] = ev.value; 
                            } 
上面这段就是多点触摸屏要用到的事件上报部分 ;  
使用一个数组 mNextData 来保存,其中 di.mAbs.mAddingPointerOffset  
是当前点的偏移量,在每个点中还在 MotionEvent 中定义了 X,Y,PRESSURE 
等偏移量,多点触摸屏的压力值由绝对坐标事件 ABS_MT_TOUCH_MAJOR 确定。 
             */ 
                      } else if (ev.type == RawInputEvent.EV_ABS && 
                                (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 
这里是对单点触摸屏上报坐标事件的新的处理方法,同样使用了数组来保存  */ 
                            if (ev.scancode == RawInputEvent.ABS_X) { 
                                di.mAbs.changed = true; 
                                di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; 
                            } else if (ev.scancode == RawInputEvent.ABS_Y) { 
                                di.mAbs.changed = true; 
                                di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; 
                            } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { 
                                di.mAbs.changed = true; 
                                di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; 
                                di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA 
                                                 + MotionEvent.SAMPLE_PRESSURE] = ev.value; 
                            } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { 
                                di.mAbs.changed = true; 
                                di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; 
                                di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA 
                                                 + MotionEvent.SAMPLE_SIZE] = ev.value; 
                            } 
             …………………………………………… .} 
下面是关键的同步处理方法  */ 
                    if (ev.type == RawInputEvent.EV_SYN 
                                && ev.scancode == RawInputEvent.SYN_MT_REPORT 
                                && di.mAbs != null) { 
在这里实现了对 SYN_MT_REPORT 事件的处理, 
改变了 di.mAbs.mAddingPointerOffset 的值,从而将 
新增的点的参数保存到下一组偏移量的位置。 
                     */ 
                                …………………… . 
                              final int newOffset = (num <= InputDevice.MAX_POINTERS) 
                                            ? (num * MotionEvent.NUM_SAMPLE_DATA) 
                                            : (InputDevice.MAX_POINTERS * 
                                                    MotionEvent.NUM_SAMPLE_DATA); 
                                    di.mAbs.mAddingPointerOffset = newOffset; 
                                    di.mAbs.mNextData[newOffset 
                                            + MotionEvent.SAMPLE_PRESSURE] = 0; 
                      } 
                        ……………… . 
                    } else if (send || (ev.type == RawInputEvent.EV_SYN 
                                && ev.scancode == RawInputEvent.SYN_REPORT)) { 
这里实现了对 SYN_REPORT 事件的处理 
如果是单点触摸屏,即使用 di.curTouchVals 数组保存的点 
转化为多点触摸屏的 mNextData 数组保存 
最后是调用 InputDevice 中的 generateAbsMotion 处理这个数组。这个函数 
的具体实现方法将在后面补充 
                    */ 
                              ………………………… .. 
                          ms.finish();           //重置所有点和偏移量 
                            …………………… .. 
}

 

由于上层的代码仍然使用ABS_X, ABS_Y 这些事件,为了使多点触摸屏代码有良好的兼容性,在 KeyInputQueue.java 的最后,我们将多点事件类型转化为单点事件类型,返回一个新的 InputDevice:

private InputDevice newInputDevice(int deviceId) { 
     int classes = getDeviceClasses(deviceId); 
String name = getDeviceName(deviceId); 
InputDevice.AbsoluteInfo absX; 
    InputDevice.AbsoluteInfo absY; 
    InputDevice.AbsoluteInfo absPressure; 
    InputDevice.AbsoluteInfo absSize; 
    if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { 
            absX = loadAbsoluteInfo(deviceId, 
                     RawInputEvent.ABS_MT_POSITION_X, "X"); 
            absY = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_MT_POSITION_Y, "Y"); 
            absPressure = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); 
            absSize = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); 
     } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 
            absX = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_X, "X"); 
            absY = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_Y, "Y"); 
            absPressure = loadAbsoluteInfo(deviceId, 
                    RawInputEvent.ABS_PRESSURE, "Pressure"); 
            absSize = loadAbsoluteInfo(deviceId,  
          RawInputEvent.ABS_TOOL_WIDTH, "Size"); 
  } else { 
            absX = null; 
            absY = null; 
            absPressure = null; 
            absSize = null; 
     }         
        return new InputDevice(deviceId, cl

 }


 

 

第四章 触摸事件 数组的处理

上面我们曾说到 generateAbsMotion 这个方法,它们在InputDevice 类的内部类 MotionState 中实现,该类被定义为 InputDevice 类的静态成员类 (static class) ,调用它们可以直接使用:

InputDeviceClass.MotionStateClass.generateAbsMotion()。

public class InputDevice { 
  …………………………… 
static class MotionState { //下面是这个内部类的几个函数 
  ……………………………… . 
/* mLastNumPointers 为上一个动作在触屏上按键的个数  */ 
int mLastNumPointers = 0; 
final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; 
/* mNextNumPointers 为下一个动作在触屏上按键的个数  */ 
/* 通过对这 2 个值大小的判断,可以确认新的动作方式  */ 
int mNextNumPointers = 0; 
final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)  
+ MotionEvent.NUM_SAMPLE_DATA]; 
………………………………… . 
     int[] generateAveragedData(int upOrDownPointer, int lastNumPointers, 
                int nextNumPointers) { 平滑处理 
     …………………………………… . 
    } 
     private boolean assignPointer(int nextIndex, boolean allowOverlap) { //指派按键 
     …………………………………… 
    } 
     private int updatePointerIdentifiers() { //更新按键 ID 
     ………………………………… . 
    } 
     void removeOldPointer(int index) { 
     …………………………………… 
    } 
     MotionEvent generateAbsMotion(InputDevice device, long curTime, 
                long curTimeNano, Display display, int orientation, 
                int metaState) { 
     …………………………………… 
int upOrDownPointer = updatePointerIdentifiers(); 
     final int numPointers = mLastNumPointers; 
     ……………………………………… 
对行为的判断  */ 
           if (nextNumPointers != lastNumPointers) { 前后在触屏上点个数不同,说明有手指 up 或 down 
                 if (nextNumPointers > lastNumPointers) {   
                    if (lastNumPointers == 0) { 上次触屏上没有按键,新值又大,说明有按键按下 
                        action = MotionEvent.ACTION_DOWN; 
                        mDownTime = curTime; 
                    } else { //有新点按下,分配给新点 ID 号 
                        action = MotionEvent.ACTION_POINTER_DOWN 
                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); 
                    } 
                } else { //新动作比原来 pointer 数量少 
                    if (numPointers == 1) { 原来只有 1 个点按下,所以现在的动作是全部按键 up 
                        action = MotionEvent.ACTION_UP; 
                    } else { 原来有多点按下,现在是 ACTION_POINTER_UP 动作, 
                        action = MotionEvent.ACTION_POINTER_UP 
                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); 
                    } 
                } 
                currentMove = null; 
           } else { 前后触屏 pointer 个数相同,所以是移动动作 ACTION_MOVE 
                action = MotionEvent.ACTION_MOVE; 
           } 
后面则是根据屏幕的 height 和 width 以及屏幕方向 orientation 对这些点进行二次处理 */ 
     …………………………………… 
    } 
MotionEvent generateRelMotion(InputDevice device, long curTime, 
                long curTimeNano, int orientation, int metaState) { 
/* 轨迹球等的处理方式  */ 
    ………………………………………… .. 
   } 
    void finish() { 结束这轮动作 
            mNextNumPointers = mAddingPointerOffset = 0; 
            mNextData[MotionEvent.SAMPLE_PRESSURE] = 0; 
   } 
…………………………………… . 
} 
……………………………… . 
…………………………………… 
}



第五章 接口

我们平时所看到的用2 个手指对图片放大缩小、旋转等手势都是由应用程序编写浏览器实现的。这些应用程序大多会使用 Android2.0 以上的在 MotionEvent.java 中实现的新的接口。所以,我们还需要给 MotionEvent 类补充尽量全的接口。这里可以完全参照 google 新的 android 代码。


第六章 总结

综上,在硬件支持基础上,Android1.6 如果要实现多点触摸功能,主要工作可简述为以下几个方面:

1、  驱动中,除了增加多点的事件上报方式,还要完全更改单点的事件上报方式。

2、  Android的 Frameworks 层需要修改的文件有: EventHub.cpp , RawInputEvent.java , KeyInputQueue.java , InputDevice.java , MotionEvent.java 。

3、  编写新的支持多点触摸功能的多媒体浏览器。

4、  为了代码简练,android2.0 在轨迹球和单点屏事件方式中也全使用了新的变量名,以方便多点屏事件同样能使用这些变量,所以修改时还需要注意许多细节方面。