[原创] Tslib Manual Calibrate On Android ——android 电阻屏幕触摸屏校准方法探讨

Wince.Android   2013-6-18 09:20 楼主

Tslib Manual Calibrate On Android

                                     --by ONCE

1.android 的坐标转换处理

This implementation is a linear transformation using 7 parameters

(a, b, c, d, e, f and s) to transform the device coordinates (Xd, Yd)

into screen coordinates (Xs, Ys) using the following equations:

s*Xs = a*Xd + b*Yd + c

s*Ys = d*Xd + e*Yd + f

Xs,Ys:LCD 坐标(也就是所谓的绝对坐标)

Xd,Yd:触摸屏坐标

在编译好了的 ANDROID 根文件系统的 system/etc/pointercal 这个文件

内,存放着 7 个数,

这 7 个数就是对应上面公式的 a,b,c,d,e,f,s

比如我原来的:

+----+-----+--------+------+--+--------+----+

  a | b |           c     |d      |e | f       |s |

+----+-----+--------+------+--+--------+----+

|-411|37818|-3636780|-51325|39|47065584|6553|

+----+-----+--------+------+--+--------+----+

2.tslib 库的移植

首先下载到一份 tslib 的代码,我用的是 tslib011.tar.gz(该包由

luzhuwei 同学友情赞助)。

将 tslib 解压到 external/下

要将一份代码编译到 android 里面去 其实没什么其他工作 主要就是那

个 Android.mk 文件的编写。网上找找 自己改改也就出来了。我的

Android.mk 文件内容如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

TSLIB_PLUGINDIR := /system/lib/ts/plugins

LOCAL_SRC_FILES := \

      src/ts_attach.c \

      src/ts_close.c \

      src/ts_config.c \

      src/ts_error.c \

      src/ts_fd.c \

      src/ts_load_module.c \

      src/ts_open.c \

      src/ts_parse_vars.c \

      src/ts_read.c \

      src/ts_read_raw.c \

      src/ts_read_raw_module.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                 /usr/include/

LOCAL_SHARED_LIBRARIES := libdl

LOCAL_MODULE := libts

include $(BUILD_SHARED_LIBRARY)

#

# plugin: input-raw

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/input-raw.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/input-raw

include $(BUILD_SHARED_LIBRARY)

#

# plugin: pthres

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/pthres.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/pthres

include $(BUILD_SHARED_LIBRARY)

#

# plugin: linear

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/linear.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/linear

include $(BUILD_SHARED_LIBRARY)

#

# plugin: dejitter

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/dejitter.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                   libts

LOCAL_MODULE := ts/plugins/dejitter

include $(BUILD_SHARED_LIBRARY)

#

# plugin: variance

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/variance.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                   libts

LOCAL_MODULE := ts/plugins/variance

include $(BUILD_SHARED_LIBRARY)

#

# ts_calibrate

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_calibrate.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  tests/ts_calibrate.h \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_calibrate

include $(BUILD_EXECUTABLE)

#

# ts_test

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_test.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_test

include $(BUILD_EXECUTABLE)

#

# ts_print

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_print.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_print

include $(BUILD_EXECUTABLE)

#

# ts_print_raw

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_print_raw.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                     libts

LOCAL_MODULE := ts_print_raw

include $(BUILD_EXECUTABLE)

#

# ts_harvest

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

       tests/fbutils.c \

       tests/font_8x8.c \

       tests/ts_harvest.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                   /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                     libts

LOCAL_MODULE := ts_harvest

include $(BUILD_EXECUTABLE)

我真不愿意粘帖上面的这个文件...太大 不过方便后人拷贝。

这里要注意是我们需要修改一下 tslib 的几个相关文件,因为 android

里的部分设备文件位置比较特殊。还有就是 tslib 里的部分变量他是

定义在 Makefile 里的 我们要把它定义到我们的文件中去。

首先 fb0 的位置变了: tests/fbutils.c

static char *defaultfbdevice = "/dev/fb0";

static char *defaultfbdevice = "/dev/graphics/fb0";

其次 plugins 的目录变了 src/ts_load_module.c

     #define PLUGIN_DIR "/system/lib/ts/plugins/"

最后 config 文件的位置变了 src/ts_config.c

     #define TS_CONF "/system/etc/ts.conf"

最后的最后不得不提到我们的配置文件 ts.conf。其他都不要动 但是

必须将它的 module_raw input 注释出来。并且 因为我们的 input 模块

编译出来之后我们命名为 input-raw 了 所以这里得改成

module_raw input-raw

下面就是编译了。发现错误就手工拷贝一下编出来的库到需要它在的

目录。

3. ts_calibrate 生成 pointercal

这里要首先不启动 android,然后进命令行手工启动 ts_calibrate 程序。

然后在屏幕上校验,完成后会生成一个正确的 pointercal 文件。

4.Android 读取校验文件 pointercal

如果要让我们的 android 被校验 那莪没我们就要按上面的公式 将

android 读取坐标部分替换掉。

“system/etc/pointercal”这个文件是被 java 程序读取使用的,文件目录:

f rameworks/base/services/java/com/android/server/InputDevice.java

看了一下这个文件的代码 发现要被修改的坐标为

             if (device.absX != null) {

                 //xxw added

                 if (device.tInfo != null){

                    scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y +

device.tInfo.z1)/ device.tInfo.s;

                    Log.i("XXW","x: "+x);

                    Log.i("XXW","trans x: "+scaledX);

                }

                else//end

                    scaledX = ((scaledX-device.absX.minValue)

                          / device.absX.range) * w;

            }

            if (device.absY != null) {

                //xxw added

                if (device.tInfo != null){

                    scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y +

device.tInfo.z2) / device.tInfo.s;

                    Log.i("XXW","y: "+y);

                    Log.i("XXW","trans y: "+scaledY);

                }

                else //end

                scaledY = ((scaledY-device.absY.minValue)

                          / device.absY.range) * h;

            }

对照上面的公式其实很容易理解 在注释 xxw 和 end 之间的代码就是

我修改的代码。其中的 device.tInfo 是我定义的一个结构,这个结构

是用来读取 pointercal 文件里的 7 个数字得到的对应的值,具体结构

如下:

  static class TransformInfo {

     float x1;

     float y1;

     float z1;

     float x2;

     float y2;

     float z2;

     float s;

};

读取文件的代码 我放在 InputDevice 的构造函数里:

//xxw added

TransformInfo t = null;

try {Log.i("XXW","InputDevice! try");

FileInputStream is = new FileInputStream(CALIBRATION_FILE);

byte[] mBuffer = new byte[64];

int len = is.read(mBuffer);

is.close();

if (len > 0) {Log.i("XXW","InputDevice! len>0");

    int i;

    for (i = 0 ; i < len ; i++) {

if (mBuffer == '\n' || mBuffer == 0) {

break;

}

    }

    len = i;

   }Log.i("XXW","InputDevice! len"+len);

   StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0,

len));

   t = new TransformInfo ();

   t.x1 = Integer.parseInt( st.nextToken() ); Log.i("XXW", "t.x1"+t.x1);

   t.y1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y1"+t.y1);

   t.z1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z1"+t.z1);

   t.x2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.x2"+t.x2);

   t.y2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y2"+t.y2);

   t.z2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z2"+t.z2);

   t.s = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.s"+t.s);

   } catch (java.io.FileNotFoundException e) {Log.i("XXW",

"FileNotFound!");

   } catch (java.io.IOException e) {Log.i("XXW", "IOException");

   }

   tInfo = t;

  Log.i("XXW","InputDevice end!");

   }

其中 static final String CALIBRATION_FILE = "/etc/pointercal";

这个时候点击一下屏幕看看是不是准确就知道是不是成功了。这里要

注意的是因为是在 InputDevice 里读取的文件 所以当我们中途替换了

pointercal 文件的时候 android 并不会再去读取这个文件。所以当我们

用 ts_calibrate 程序生成 pointercal 文件之后需要重启一下。同样 当我

们修改或者删除了那个文件 我们也是需要重启的。所以这个文档 的

名字叫手工校验。

PS:

如何自动化?

如何用 android 应用程序校验而不是命令行?

如何不需要重启也能实现?

请听下回分解。

话接上回,我们发现了手工利用tslib校验触摸屏的缺点。那么这一回我们就来一次稍微高级一点的校验吧。

我们其实只需要相对的x,y以及lcd的x,y就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。


其实一切我们打算做的事情可以都在InputDevice里面做完

下面我把完整之后整个的InputDevice贴出来:

/*

* Copyright (C) 2007 The Android Open Source Project

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*      http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

package com.android.server;

import android.util.Log;

import android.view.Display;

import android.view.MotionEvent;

import android.view.Surface;

import android.view.WindowManagerPolicy;

import java.io.FileInputStream;

import java.util.StringTokenizer;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import android.os.SystemProperties;

public class InputDevice {

    /** Amount that trackball needs to move in order to generate a key event. */

    static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;


    //once edit

    static final String CALIBRATION_FILE = "/data/etc/pointercal";

    //edit ends


    final int id;

    final int classes;

    final String name;

    final AbsoluteInfo absX;

    final AbsoluteInfo absY;

    final AbsoluteInfo absPressure;

    final AbsoluteInfo absSize;

       //once edit

    static TransformInfo tInfo;

    //edit ends

    long mDownTime = 0;

    int mMetaKeysState = 0;

    static File desFile;   

    final MotionState mAbs = new MotionState(0, 0);

    final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD,

            TRACKBALL_MOVEMENT_THRESHOLD);


    static class MotionState {

        int xPrecision;

        int yPrecision;

        float xMoveScale;

        float yMoveScale;

        MotionEvent currentMove = null;

        boolean changed = false;

        boolean down = false;

        boolean lastDown = false;

        long downTime = 0;

        int x = 0;

        int y = 0;

        int pressure = 1;

        int size = 0;


        MotionState(int mx, int my) {

            xPrecision = mx;

            yPrecision = my;

            xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f;

            yMoveScale = my != 0 ? (1.0f/my) : 1.0f;

        }


        MotionEvent generateMotion(InputDevice device, long curTime,

                boolean isAbs, Display display, int orientation,

                int metaState) {

            if (!changed) {

                return null;

            }

           //once edit

        //String prop = System.getProperty("ts.config.calibrate", "noset");

        String prop = SystemProperties.get("ts.config.calibrate", "noset");

        if (prop.equalsIgnoreCase("start")){

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase start");

            device.tInfo = null;

        }else if (prop.equalsIgnoreCase("done")){

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase done");

            readCalibrate();

            SystemProperties.set("ts.config.calibrate", "end");

        }else{

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase else");

        }

        //edit ends

            float scaledX = x;

            float scaledY = y;

            float temp;

            float scaledPressure = 1.0f;

            float scaledSize = 0;

            int edgeFlags = 0;

            if (isAbs) {

                int w = display.getWidth()-1;

                int h = display.getHeight()-1;

                if (orientation == Surface.ROTATION_90

                        || orientation == Surface.ROTATION_270) {

                    int tmp = w;

                    w = h;

                    h = tmp;

                }

                if (device.absX != null) {

                      //once edit

                    if (device.tInfo != null){

                        scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y + device.tInfo.z1)/ device.tInfo.s;

                     Log.i("XXW","x: "+x);

                     Log.i("XXW","trans x: "+scaledX);

                    }

                    else

                    //edit ends

                      scaledX = ((scaledX-device.absX.minValue)

                                / device.absX.range) * w;

                }

                if (device.absY != null) {

                    //once edit

                    if (device.tInfo != null){

                        scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y + device.tInfo.z2) / device.tInfo.s;

                    Log.i("XXW","y: "+y);

                    Log.i("XXW","trans y: "+scaledY);

                    }

                    else

                    //edit ends

                       scaledY = ((scaledY-device.absY.minValue)

                                / device.absY.range) * h;

                }

                if (device.absPressure != null) {

                    scaledPressure =

                        ((pressure-device.absPressure.minValue)

                                / (float)device.absPressure.range);

                }

                if (device.absSize != null) {

                    scaledSize =

                        ((size-device.absSize.minValue)

                                / (float)device.absSize.range);

                }

                switch (orientation) {

                    case Surface.ROTATION_90:

                        temp = scaledX;

                        scaledX = scaledY;

                        scaledY = w-temp;

                        break;

                    case Surface.ROTATION_180:

                        scaledX = w-scaledX;

                        scaledY = h-scaledY;

                        break;

                    case Surface.ROTATION_270:

                        temp = scaledX;

                        scaledX = h-scaledY;

                        scaledY = temp;

                        break;

                }

                if (scaledX == 0) {

                    edgeFlags += MotionEvent.EDGE_LEFT;

                } else if (scaledX == display.getWidth() - 1.0f) {

                    edgeFlags += MotionEvent.EDGE_RIGHT;

                }


                if (scaledY == 0) {

                    edgeFlags += MotionEvent.EDGE_TOP;

                } else if (scaledY == display.getHeight() - 1.0f) {

                    edgeFlags += MotionEvent.EDGE_BOTTOM;

                }


            } else {

                scaledX *= xMoveScale;

                scaledY *= yMoveScale;

                switch (orientation) {

                    case Surface.ROTATION_90:

                        temp = scaledX;

                        scaledX = scaledY;

                        scaledY = -temp;

                        break;

                    case Surface.ROTATION_180:

                        scaledX = -scaledX;

                        scaledY = -scaledY;

                        break;

                    case Surface.ROTATION_270:

                        temp = scaledX;

                        scaledX = -scaledY;

                        scaledY = temp;

                        break;

                }

            }


            changed = false;

            if (down != lastDown) {

                int action;

                lastDown = down;

                if (down) {

                    action = MotionEvent.ACTION_DOWN;

                    downTime = curTime;

                } else {

                    action = MotionEvent.ACTION_UP;

                }

                currentMove = null;

                if (!isAbs) {

                    x = y = 0;

                }

                return MotionEvent.obtain(downTime, curTime, action,

                        scaledX, scaledY, scaledPressure, scaledSize, metaState,

                        xPrecision, yPrecision, device.id, edgeFlags);

            } else {

                if (currentMove != null) {

                    if (false) Log.i("InputDevice", "Adding batch x=" + scaledX

                            + " y=" + scaledY + " to " + currentMove);

                    currentMove.addBatch(curTime, scaledX, scaledY,

                            scaledPressure, scaledSize, metaState);

                    if (WindowManagerPolicy.WATCH_POINTER) {

                        Log.i("KeyInputQueue", "Updating: " + currentMove);

                    }

                    return null;

                }

                MotionEvent me = MotionEvent.obtain(downTime, curTime,

                        MotionEvent.ACTION_MOVE, scaledX, scaledY,

                        scaledPressure, scaledSize, metaState,

                        xPrecision, yPrecision, device.id, edgeFlags);

                currentMove = me;

                return me;

            }

        }

    }


    static class AbsoluteInfo {

        int minValue;

        int maxValue;

        int range;

        int flat;

        int fuzz;

    };

       //once edit

    static class TransformInfo {

       float x1;

       float y1;

       float z1;

       float x2;

       float y2;

       float z2;

       float s;

    };

    //edit ends


    InputDevice(int _id, int _classes, String _name,

            AbsoluteInfo _absX, AbsoluteInfo _absY,

            AbsoluteInfo _absPressure, AbsoluteInfo _absSize) {

        id = _id;

        classes = _classes;

        name = _name;

        absX = _absX;

        absY = _absY;

        absPressure = _absPressure;

        absSize = _absSize;


        //once edit

        desFile = new File(CALIBRATION_FILE);

        readCalibrate();

        //edit ends

    }

    static void readCalibrate(){

        //xxw added

        Log.i("XXW","readCalibrate!");

        TransformInfo t = null;

        try {

            FileInputStream is = new FileInputStream(CALIBRATION_FILE);

            byte[] mBuffer = new byte[64];

            int len = is.read(mBuffer);

            is.close();


            if (len > 0) {

                int i;

                for (i = 0 ; i < len ; i++) {

                    if (mBuffer == '\n' || mBuffer == 0) {

                        break;

                    }

                }

                len = i;

            }


            StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0, len));

            t = new TransformInfo ();

            t.x1 = Integer.parseInt( st.nextToken() );

            t.y1 = Integer.parseInt( st.nextToken() );

            t.z1 = Integer.parseInt( st.nextToken() );

            t.x2 = Integer.parseInt( st.nextToken() );

            t.y2 = Integer.parseInt( st.nextToken() );

            t.z2 = Integer.parseInt( st.nextToken() );

            t.s = Integer.parseInt( st.nextToken() );

        } catch (java.io.FileNotFoundException e) {

            Log.i("XXW", "FileNotFound!");

        } catch (java.io.IOException e) {

            Log.i("XXW", "IOException");

        }

        tInfo = t;

        Log.i("XXW","readCalibrate done!");

    }

};



与上一次的那个InputDevice相比我将读取校准文件的代码单独的变成一个函数,之所以这么做是因为我们打算不重启就可以直接让android校准完成。这里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件然后将系统属性变成其他值不再读取文件。


下面我们就要写一个apk来实现校准了。

这里我尝试了2种方法一种是纯java的apk 一种是jni的apk。其实对于校准来说上层已经能拿到x,y那么我们取5个点就已经可以算出来那7个校准值然后利用java存文件了。整个apk的java代码


package com.android.calibrate;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import android.util.Log;

public class Calibrate {

    private calibration cal;

    public Calibrate() {

        cal = new calibration();

    }

    class calibration {

        int x[] = new int[5];

        int xfb[] = new int[5];

        int y[] = new int[5];

        int yfb[] = new int[5];

        int a[] = new int[7];

    };

    boolean perform_calibration() {

        Log.i("XXW", "perform_calibration");

        int j;

        float n, x, y, x2, y2, xy, z, zx, zy;

        float det, a, b, c, e, f, i;

        float scaling = (float)65536.0;

        // Get sums for matrix

        n = x = y = x2 = y2 = xy = 0;

        for (j = 0; j < 5; j++) {

            n += 1.0;

            x += (float)cal.x[j];

            y += (float)cal.y[j];

            x2 += (float)(cal.x[j] * cal.x[j]);

            y2 += (float)(cal.y[j] * cal.y[j]);

            xy += (float)(cal.x[j] * cal.y[j]);

        }

        // Get determinant of matrix -- check if determinant is too small

        det = n * (x2 * y2 - xy * xy) + x * (xy * y - x * y2) + y * (x * xy - y * x2);

        if (det < 0.1 && det > -0.1) {

            Log.i("ts_calibrate: determinant is too small -- %f\n", "" + det);

            return false;

        }

        // Get elements of inverse matrix

        a = (x2 * y2 - xy * xy) / det;

        b = (xy * y - x * y2) / det;

        c = (x * xy - y * x2) / det;

        e = (n * y2 - y * y) / det;

        f = (x * y - n * xy) / det;

        i = (n * x2 - x * x) / det;

        // Get sums for x calibration

        z = zx = zy = 0;

        for (j = 0; j < 5; j++) {

            z += (float)cal.xfb[j];

            zx += (float)(cal.xfb[j] * cal.x[j]);

            zy += (float)(cal.xfb[j] * cal.y[j]);

        }

        // Now multiply out to get the calibration for framebuffer x coord

        cal.a[0] = (int)((a * z + b * zx + c * zy) * (scaling));

        cal.a[1] = (int)((b * z + e * zx + f * zy) * (scaling));

        cal.a[2] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c

                * z + f * zx + i * zy));

        // Get sums for y calibration

        z = zx = zy = 0;

        for (j = 0; j < 5; j++) {

            z += (float)cal.yfb[j];

            zx += (float)(cal.yfb[j] * cal.x[j]);

            zy += (float)(cal.yfb[j] * cal.y[j]);

        }

        // Now multiply out to get the calibration for framebuffer y coord

        cal.a[3] = (int)((a * z + b * zx + c * zy) * (scaling));

        cal.a[4] = (int)((b * z + e * zx + f * zy) * (scaling));

        cal.a[5] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c

                * z + f * zx + i * zy));

        // If we got here, we're OK, so assign scaling to a[6] and return

        cal.a[6] = (int)scaling;

        return true;

        /*

         * // This code was here originally to just insert default values

         * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1;

         * return 1;

         */

    }

    void get_sample(int index, int x1, int y1, int x, int y) {

        Log.i("XXW", "get_sample");

        cal.x[index] = x1;

        cal.y[index] = y1;

        cal.xfb[index] = x;

        cal.yfb[index] = y;

    }

    int calibrate_main() {

        int result = 0;

        Log.i("XXW", "calibrate_main");

        if (perform_calibration()) {

            String strPara = String.format("%d %d %d %d %d %d %d", cal.a[1], cal.a[2], cal.a[0],

                    cal.a[4], cal.a[5], cal.a[3], cal.a[6]);

            boolean success = new File("/data/etc").mkdir();

            if (!success) {

                Log.i(this.toString(), "no success");

            }

            File desFile = new File("/data/etc/pointercal");

            if (!desFile.exists())

                try {

                    desFile.createNewFile();

                } catch (IOException e1) {

                    e1.printStackTrace();

                }

            FileOutputStream fos;

            try {

                fos = new FileOutputStream(desFile);

                byte[] buf = strPara.getBytes();

                int bytesRead = buf.length;

                try {

                    fos.write(buf, 0, bytesRead);

                    fos.flush();

                    fos.close();

                } catch (IOException e) {                  

                    e.printStackTrace();

                }

            } catch (FileNotFoundException e) {            

                e.printStackTrace();

            }

            result = 0;

        } else {

            result = -1;

        }

        return result;

    }

}



其实这个就是tslib里那个ts_calibrate.cpp的姐妹篇。重要的函数就一个perform_calibration() 。这里要注意的是你必须首先利用get_sample读取5个点的值来初始化cal然后再调用calibrate_main来计算校验系数。而且那5个点的顺序为左上 右上 右下 左下 中间。这4个点基本上应该在屏幕的边缘附近否则计算出来的校验值可能不准。 利用上面的那个类写一个apk出来跑跑看流程应该就可以了。


package com.android.calibrate;

import com.android.calibrate.R;

import android.app.Activity;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.os.Bundle;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.Window;

import android.view.WindowManager;

import android.view.View.OnTouchListener;

import android.widget.Toast;

import android.os.SystemProperties;

public class AndroidCalibrate extends Activity {

    final String TAG = "ScreenCalibration";

    final int UI_SCREEN_WIDTH = 800;

    final int UI_SCREEN_HEIGHT = 480;

    CrossView myview;

    int direction;

    private Calibrate cal;

    int xList[] = {

            50, UI_SCREEN_WIDTH - 50, UI_SCREEN_WIDTH - 50, 50, UI_SCREEN_WIDTH / 2

    };

    int yList[] = {

            50, UI_SCREEN_HEIGHT - 50, UI_SCREEN_HEIGHT - 50, 50, UI_SCREEN_HEIGHT / 2

    };

    static void setNotTitle(Activity act) {

        act.requestWindowFeature(Window.FEATURE_NO_TITLE);

    }

    static void setFullScreen(Activity act) {

        setNotTitle(act);

        act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

                WindowManager.LayoutParams.FLAG_FULLSCREEN);

    }

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setFullScreen(this);   

        myview = new CrossView(this);

        setContentView(myview);


        SystemProperties.set("ts.config.calibrate", "start");


        cal = new Calibrate();

        direction = 0;


        myview.setOnTouchListener(new OnTouchListener() {

            @Override

            public boolean onTouch(View v, MotionEvent event) {

                Log.i("OnTouch", event.getX() + "," + event.getY());

                v.invalidate();

                if (direction < 4) {

                    Log.i("XXW time onTouchListener", " " + direction);

                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),

                            xList[direction], yList[direction]);

                }

                if (direction == 4) {

                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),

                            xList[direction], yList[direction]);

                    Log.i("XXW", "calibrate_main");

                    cal.calibrate_main();

                    Toast.makeText(getBaseContext(), "Calibrate Done!", Toast.LENGTH_SHORT).show();

                    SystemProperties.set("ts.config.calibrate", "done");

                    AndroidCalibrate.this.finish();

                }

                direction++;

                return false;

            }

        });

    }

    public class CrossView extends View {

        public CrossView(Context context) {

            super(context);

        }

        public void onDraw(Canvas canvas) {

            Paint paint = new Paint();

            paint.setColor(Color.GREEN);

            if (direction == 0) {

                canvas.drawLine(40, 50, 60, 50, paint);

                canvas.drawLine(50, 40, 50, 60, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 1) {

                canvas.drawLine(UI_SCREEN_WIDTH - 60, 50, UI_SCREEN_WIDTH - 40, 50, paint);

                canvas.drawLine(UI_SCREEN_WIDTH - 50, 40, UI_SCREEN_WIDTH - 50, 60, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 2) {

                canvas.drawLine(UI_SCREEN_WIDTH - 60, UI_SCREEN_HEIGHT - 50, UI_SCREEN_WIDTH - 40,

                        UI_SCREEN_HEIGHT - 50, paint);

                canvas.drawLine(UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 60, UI_SCREEN_WIDTH - 50,

                        UI_SCREEN_HEIGHT - 40, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 3) {

                canvas.drawLine(40, UI_SCREEN_HEIGHT - 50, 60, UI_SCREEN_HEIGHT - 50, paint);

                canvas.drawLine(50, UI_SCREEN_HEIGHT - 60, 50, UI_SCREEN_HEIGHT - 40, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 4) {

                canvas.drawLine(UI_SCREEN_WIDTH / 2 - 10, UI_SCREEN_HEIGHT / 2,

                        UI_SCREEN_WIDTH / 2 + 10, UI_SCREEN_HEIGHT / 2, paint);

                canvas.drawLine(UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 - 10,

                        UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 + 10, paint);

                paint.setColor(Color.WHITE);

            } else {

            }

            // canvas.drawText(getResources().getString(R.string.

            // screen_calibration_content),

            // UI_SCREEN_WIDTH / 2 - 50, UI_SCREEN_HEIGHT / 2, paint);

            super.onDraw(canvas);

        }

    }

}



原文 http://xxw8393.blog.163.com/blog/static/37256834200988102647150/ 写的十分好,值得学习!
如果对linux,Android,wince 等嵌入式底层有兴趣的,请加这个QQ群吧,群号:27100460

回复评论 (5)

Android自身不带触摸屏校验APP,我们一般都是采用tslib来完成触摸屏校验,网上的相关文章也不少,在此我记录下我的移植过程。

1.准备工作

     首先我们要下载tslib的源码,以前在做QT时直接下载tslib源码,网上盛传的也很多,所以很容易下载,但是在此,我并非用的是tslib,而是tslibonandroid,到底与tslib有什么差异,我没仔细研究过,可能是太懒的缘故吧。

2.开始工作

   我们现在就开始移植工作吧。首先把你的tslibonandroid文件包解压,放入external\tslibonandroid目录,这个文件夹中的tests\ts_calibrate.c具体实现触摸屏的校验,具体情形之后分析。

   Android平台APP是要跑Java的,所以我们在此必须建立一个Java应用程序,实现我们在显示屏上的操作。在\development\calibrate\app_calibrate\src\com\android\calibrate添加源码,具体代码如下:

package com.android.calibrate;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class Calibrate extends Activity     @Override
    public void onCreate(Bundle savedInstanceState)         super.onCreate(savedInstanceState);         TextView tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv); //        super.onCreate(savedInstanceState);
//        setContentView(R.layout.main);
  tsmainloop();           public native void tsmainloop();

    static {
        System.loadLibrary("calibrate-jni"); }

有了Java程序也不行,因为我们需要Java本地接口(即JNI),来实现Java与C本地库的交互,在development\calibrate\app_calibrate\ts_calibrate\jni\添加我们的JNI源码,代码如下:

#include
#include
#include
#include "../../../external/tslibonandroid/tests/calibratejni.h"

static void tsmainloop(JNIEnv *env, jobject object) LOGE("hello jni calirate\n");
ts_main();
}

static const char *classPathName = "com/android/calibrate/Calibrate";

static JNINativeMethod methods[] = {                             //方法列表
{"tsmainloop", "()V", (void*)tsmainloop },
};

/*
* Register several native methods for one class. static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)               //方法的注册     jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        fprintf(stderr, "Native registration unable to find class '%s'", className);
        return JNI_FALSE;     if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        fprintf(stderr, "RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
* Register native methods for all classes we know about. static int registerNatives(JNIEnv* env) if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
}

return JNI_TRUE;
}

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

jint JNI_OnLoad(JavaVM* vm, void* reserved)     UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv* env = NULL;     printf("JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        fprintf(stderr, "GetEnv failed");
        goto bail;     env = uenv.env;

    if (!registerNatives(env)) {
        fprintf(stderr, "registerNatives failed");     
    result = JNI_VERSION_1_4; bail:
    return result; 这部分主要实现JNI的注册及对下层提供调用接口。

3.touch校验

   上面有提到ts_main(),它具体在\external\tslibonandroid\tests\ts_calibrate.c文件中,主要是实现5点校验。

#define TSLIB_TSDEVICE   "/dev/input/event1"           
#define DEVICE_NAME      "/dev/myts"                          //myts设备名
#define TSLIB_CALIBFILE "/system/etc/pointercal"
#define TS_POINTERCAL    "/system/etc/pointercal" int ts_main() struct tsdev *ts;
    calibration cal;
int cal_fd;
    char cal_buffer[256];
    char *tsdevice = NULL;
    char *calfile = NULL;
    unsigned int i;
    unsigned int ret;

    signal(SIGSEGV, sig);
    signal(SIGINT, sig);
    signal(SIGTERM, sig);

    int size;
    char buffer[5] = "OK";
    int fd = open("/dev/myts",O_RDWR);
    if(fd == -1)    LOGE("...open device failed 1...");
   return -1;     printf("fd: %d\n",fd);
    write(fd,buffer,2);
    close(fd);

    if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL )    ts = ts_open(tsdevice,0);
   LOGE("---tsdevice:%s getenv(TSLIB_TSDEVICE)!=NULL-----\n",tsdevice);     else       if (!(ts = ts_open("/dev/input/event1", 0)))    LOGE("---ts_open(/dev/input/event1)---failed\n");   
   ts = ts_open("/dev/touchscreen/ucb1x00", 0);      }

LOGE("ts configure\n");
if (!ts) {
   LOGE("ts open failed\n");
   ret = -1;
   goto end; if (ts_config(ts)) {
   perror("ts_config");
   LOGE("ts config failed\n");
   ret = -1;
   goto end;
}

    if (open_framebuffer()) {
   close_framebuffer();
   LOGE("open_framebuffer failed\n");
   ret = -1;
   goto end;
}

for(i = 0; i < NR_COLORS; i++)
   setcolor (i, palette );

   put_string_center (xres / 2, yres / 4, "TSLIB calibration utility", 1);
   put_string_center (xres / 2, yres / 4 + 20, "Touch crosshair to calibrate", 2);

   // printf("xres = %d, yres = %d\n", xres, yres);

   // Clear the buffer
   clearbuf(ts);

   get_sample (ts, &cal, 0, 50,        50,        "Top left");
   clearbuf(ts);
   get_sample (ts, &cal, 1, xres - 50, 50,        "Top right");
   clearbuf(ts);
   get_sample (ts, &cal, 2, xres - 50, yres - 50, "Bot right");
   clearbuf(ts);
   get_sample (ts, &cal, 3, 50,        yres - 50, "Bot left");
   clearbuf(ts);
   get_sample (ts, &cal, 4, xres / 2, yres / 2, "Center");

if(perform_calibration (&cal)) {
   LOGE("Calibration constants: ");
   for (i = 0; i < 7; i++)
    LOGE("%d ", cal.a );    if ((calfile = getenv("TSLIB_CALIBFILE")) != NULL) {
    cal_fd = open (calfile, O_CREAT | O_RDWR,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    LOGE("getenv(TSLIB_CALIBFILE) OK");
   } else {
    cal_fd = open (TS_POINTERCAL, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    LOGE("getenv(TSLIB_CALIBFILE) NULL");    sprintf (cal_buffer,"%d %d %d %d %d %d %d",cal.a[1], cal.a[2], cal.a[0],cal.a[4], cal.a[5], cal.a[3], cal.a[6]);
   write (cal_fd, cal_buffer, strlen (cal_buffer) + 1);
   close (cal_fd);
   i = 0;
} else {
   LOGE("Calibration failed...");
   i = -1; close_framebuffer();
goto end2;

end:
   i = ret;
end2:
   #if 1
   strcpy(buffer,"END");
   fd = open("/dev/myts",O_RDWR);
   if(fd == -1)     //   printf("...open device failed...\n");
    LOGE("...open device failed 2...");
    return -1;    printf("fd: %d\n",fd);
   write(fd,buffer,3);
   close(fd);
   #endif return i;   
}
如果对linux,Android,wince 等嵌入式底层有兴趣的,请加这个QQ群吧,群号:27100460
点赞  2013-6-18 10:04

开始我在那里郁闷,以为没人做过android 电阻屏幕的触摸屏校准,
原来是我搜索对关键词,汗!呵呵,现在参考一下自己也能做出来的。
如果对linux,Android,wince 等嵌入式底层有兴趣的,请加这个QQ群吧,群号:27100460
点赞  2013-6-18 10:05

转:http://blog.163.com/fenglang_200 ... 318200911172427618/

Tslib Manual Calibrate On Android

                                     --by ONCE

1.android 的坐标转换处理

This implementation is a linear transformation using 7 parameters

(a, b, c, d, e, f and s) to transform the device coordinates (Xd, Yd)

into screen coordinates (Xs, Ys) using the following equations:

s*Xs = a*Xd + b*Yd + c

s*Ys = d*Xd + e*Yd + f

Xs,Ys:LCD 坐标(也就是所谓的绝对坐标)

Xd,Yd:触摸屏坐标

在编译好了的 ANDROID 根文件系统的 system/etc/pointercal 这个文件

内,存放着 7 个数,

这 7 个数就是对应上面公式的 a,b,c,d,e,f,s

比如我原来的:

+----+-----+--------+------+--+--------+----+

  a | b |           c     |d      |e | f       |s |

+----+-----+--------+------+--+--------+----+

|-411|37818|-3636780|-51325|39|47065584|6553|

+----+-----+--------+------+--+--------+----+

2.tslib 库的移植

首先下载到一份 tslib 的代码,我用的是 tslib011.tar.gz(该包由

luzhuwei 同学友情赞助)。

将 tslib 解压到 external/下

要将一份代码编译到 android 里面去 其实没什么其他工作 主要就是那

个 Android.mk 文件的编写。网上找找 自己改改也就出来了。我的

Android.mk 文件内容如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

TSLIB_PLUGINDIR := /system/lib/ts/plugins

LOCAL_SRC_FILES := \

      src/ts_attach.c \

      src/ts_close.c \

      src/ts_config.c \

      src/ts_error.c \

      src/ts_fd.c \

      src/ts_load_module.c \

      src/ts_open.c \

      src/ts_parse_vars.c \

      src/ts_read.c \

      src/ts_read_raw.c \

      src/ts_read_raw_module.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                 /usr/include/

LOCAL_SHARED_LIBRARIES := libdl

LOCAL_MODULE := libts

include $(BUILD_SHARED_LIBRARY)

#

# plugin: input-raw

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/input-raw.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/input-raw

include $(BUILD_SHARED_LIBRARY)

#

# plugin: pthres

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/pthres.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/pthres

include $(BUILD_SHARED_LIBRARY)

#

# plugin: linear

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/linear.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                 libts

LOCAL_MODULE := ts/plugins/linear

include $(BUILD_SHARED_LIBRARY)

#

# plugin: dejitter

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/dejitter.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                   libts

LOCAL_MODULE := ts/plugins/dejitter

include $(BUILD_SHARED_LIBRARY)

#

# plugin: variance

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := plugins/variance.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/

LOCAL_SHARED_LIBRARIES := libdl \

                   libts

LOCAL_MODULE := ts/plugins/variance

include $(BUILD_SHARED_LIBRARY)

#

# ts_calibrate

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_calibrate.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  tests/ts_calibrate.h \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_calibrate

include $(BUILD_EXECUTABLE)

#

# ts_test

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_test.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_test

include $(BUILD_EXECUTABLE)

#

# ts_print

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_print.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                    libts

LOCAL_MODULE := ts_print

include $(BUILD_EXECUTABLE)

#

# ts_print_raw

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

      tests/fbutils.c \

      tests/font_8x8.c \

      tests/ts_print_raw.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                  /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                     libts

LOCAL_MODULE := ts_print_raw

include $(BUILD_EXECUTABLE)

#

# ts_harvest

#

include $(CLEAR_VARS)

LOCAL_SRC_FILES := tests/testutils.c \

       tests/fbutils.c \

       tests/font_8x8.c \

       tests/ts_harvest.c

LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \

                   /usr/include/

LOCAL_SHARED_LIBRARIES := libdl \

                     libts

LOCAL_MODULE := ts_harvest

include $(BUILD_EXECUTABLE)

我真不愿意粘帖上面的这个文件...太大 不过方便后人拷贝。

这里要注意是我们需要修改一下 tslib 的几个相关文件,因为 android

里的部分设备文件位置比较特殊。还有就是 tslib 里的部分变量他是

定义在 Makefile 里的 我们要把它定义到我们的文件中去。

首先 fb0 的位置变了: tests/fbutils.c

static char *defaultfbdevice = "/dev/fb0";

static char *defaultfbdevice = "/dev/graphics/fb0";

其次 plugins 的目录变了 src/ts_load_module.c

     #define PLUGIN_DIR "/system/lib/ts/plugins/"

最后 config 文件的位置变了 src/ts_config.c

     #define TS_CONF "/system/etc/ts.conf"

最后的最后不得不提到我们的配置文件 ts.conf。其他都不要动 但是

必须将它的 module_raw input 注释出来。并且 因为我们的 input 模块

编译出来之后我们命名为 input-raw 了 所以这里得改成

module_raw input-raw

下面就是编译了。发现错误就手工拷贝一下编出来的库到需要它在的

目录。

3. ts_calibrate 生成 pointercal

这里要首先不启动 android,然后进命令行手工启动 ts_calibrate 程序。

然后在屏幕上校验,完成后会生成一个正确的 pointercal 文件。

4.Android 读取校验文件 pointercal

如果要让我们的 android 被校验 那莪没我们就要按上面的公式 将

android 读取坐标部分替换掉。

“system/etc/pointercal”这个文件是被 java 程序读取使用的,文件目录:

f rameworks/base/services/java/com/android/server/InputDevice.java

看了一下这个文件的代码 发现要被修改的坐标为

             if (device.absX != null) {

                 //xxw added

                 if (device.tInfo != null){

                    scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y +

device.tInfo.z1)/ device.tInfo.s;

                    Log.i("XXW","x: "+x);

                    Log.i("XXW","trans x: "+scaledX);

                }

                else//end

                    scaledX = ((scaledX-device.absX.minValue)

                          / device.absX.range) * w;

            }

            if (device.absY != null) {

                //xxw added

                if (device.tInfo != null){

                    scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y +

device.tInfo.z2) / device.tInfo.s;

                    Log.i("XXW","y: "+y);

                    Log.i("XXW","trans y: "+scaledY);

                }

                else //end

                scaledY = ((scaledY-device.absY.minValue)

                          / device.absY.range) * h;

            }

对照上面的公式其实很容易理解 在注释 xxw 和 end 之间的代码就是

我修改的代码。其中的 device.tInfo 是我定义的一个结构,这个结构

是用来读取 pointercal 文件里的 7 个数字得到的对应的值,具体结构

如下:

  static class TransformInfo {

     float x1;

     float y1;

     float z1;

     float x2;

     float y2;

     float z2;

     float s;

};

读取文件的代码 我放在 InputDevice 的构造函数里:

//xxw added

TransformInfo t = null;

try {Log.i("XXW","InputDevice! try");

FileInputStream is = new FileInputStream(CALIBRATION_FILE);

byte[] mBuffer = new byte[64];

int len = is.read(mBuffer);

is.close();

if (len > 0) {Log.i("XXW","InputDevice! len>0");

    int i;

    for (i = 0 ; i < len ; i++) {

if (mBuffer == '\n' || mBuffer == 0) {

break;

}

    }

    len = i;

   }Log.i("XXW","InputDevice! len"+len);

   StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0,

len));

   t = new TransformInfo ();

   t.x1 = Integer.parseInt( st.nextToken() ); Log.i("XXW", "t.x1"+t.x1);

   t.y1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y1"+t.y1);

   t.z1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z1"+t.z1);

   t.x2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.x2"+t.x2);

   t.y2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y2"+t.y2);

   t.z2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z2"+t.z2);

   t.s = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.s"+t.s);

   } catch (java.io.FileNotFoundException e) {Log.i("XXW",

"FileNotFound!");

   } catch (java.io.IOException e) {Log.i("XXW", "IOException");

   }

   tInfo = t;

  Log.i("XXW","InputDevice end!");

   }

其中 static final String CALIBRATION_FILE = "/etc/pointercal";

这个时候点击一下屏幕看看是不是准确就知道是不是成功了。这里要

注意的是因为是在 InputDevice 里读取的文件 所以当我们中途替换了

pointercal 文件的时候 android 并不会再去读取这个文件。所以当我们

用 ts_calibrate 程序生成 pointercal 文件之后需要重启一下。同样 当我

们修改或者删除了那个文件 我们也是需要重启的。所以这个文档 的

名字叫手工校验。

PS:

如何自动化?

如何用 android 应用程序校验而不是命令行?

如何不需要重启也能实现?

请听下回分解。

话接上回,我们发现了手工利用tslib校验触摸屏的缺点。那么这一回我们就来一次稍微高级一点的校验吧。

我们其实只需要相对的x,y以及lcd的x,y就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。

其实一切我们打算做的事情可以都在InputDevice里面做完

下面我把完整之后整个的InputDevice贴出来:

/*

* Copyright (C) 2007 The Android Open Source Project

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*      http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

package com.android.server;

import android.util.Log;

import android.view.Display;

import android.view.MotionEvent;

import android.view.Surface;

import android.view.WindowManagerPolicy;

import java.io.FileInputStream;

import java.util.StringTokenizer;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import android.os.SystemProperties;

public class InputDevice {

    /** Amount that trackball needs to move in order to generate a key event. */

    static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;


    //once edit

    static final String CALIBRATION_FILE = "/data/etc/pointercal";

    //edit ends


    final int id;

    final int classes;

    final String name;

    final AbsoluteInfo absX;

    final AbsoluteInfo absY;

    final AbsoluteInfo absPressure;

    final AbsoluteInfo absSize;

       //once edit

    static TransformInfo tInfo;

    //edit ends

    long mDownTime = 0;

    int mMetaKeysState = 0;

    static File desFile;   

    final MotionState mAbs = new MotionState(0, 0);

    final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD,

            TRACKBALL_MOVEMENT_THRESHOLD);


    static class MotionState {

        int xPrecision;

        int yPrecision;

        float xMoveScale;

        float yMoveScale;

        MotionEvent currentMove = null;

        boolean changed = false;

        boolean down = false;

        boolean lastDown = false;

        long downTime = 0;

        int x = 0;

        int y = 0;

        int pressure = 1;

        int size = 0;


        MotionState(int mx, int my) {

            xPrecision = mx;

            yPrecision = my;

            xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f;

            yMoveScale = my != 0 ? (1.0f/my) : 1.0f;

        }


        MotionEvent generateMotion(InputDevice device, long curTime,

                boolean isAbs, Display display, int orientation,

                int metaState) {

            if (!changed) {

                return null;

            }

           //once edit

        //String prop = System.getProperty("ts.config.calibrate", "noset");

        String prop = SystemProperties.get("ts.config.calibrate", "noset");

        if (prop.equalsIgnoreCase("start")){

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase start");

            device.tInfo = null;

        }else if (prop.equalsIgnoreCase("done")){

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase done");

            readCalibrate();

            SystemProperties.set("ts.config.calibrate", "end");

        }else{

            Log.i("XXW prop", prop);

            Log.i("XXW", "prop.equalsIgnoreCase else");

        }

        //edit ends

            float scaledX = x;

            float scaledY = y;

            float temp;

            float scaledPressure = 1.0f;

            float scaledSize = 0;

            int edgeFlags = 0;

            if (isAbs) {

                int w = display.getWidth()-1;

                int h = display.getHeight()-1;

                if (orientation == Surface.ROTATION_90

                        || orientation == Surface.ROTATION_270) {

                    int tmp = w;

                    w = h;

                    h = tmp;

                }

                if (device.absX != null) {

                      //once edit

                    if (device.tInfo != null){

                        scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y + device.tInfo.z1)/ device.tInfo.s;

                     Log.i("XXW","x: "+x);

                     Log.i("XXW","trans x: "+scaledX);

                    }

                    else

                    //edit ends

                      scaledX = ((scaledX-device.absX.minValue)

                                / device.absX.range) * w;

                }

                if (device.absY != null) {

                    //once edit

                    if (device.tInfo != null){

                        scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y + device.tInfo.z2) / device.tInfo.s;

                    Log.i("XXW","y: "+y);

                    Log.i("XXW","trans y: "+scaledY);

                    }

                    else

                    //edit ends

                       scaledY = ((scaledY-device.absY.minValue)

                                / device.absY.range) * h;

                }

                if (device.absPressure != null) {

                    scaledPressure =

                        ((pressure-device.absPressure.minValue)

                                / (float)device.absPressure.range);

                }

                if (device.absSize != null) {

                    scaledSize =

                        ((size-device.absSize.minValue)

                                / (float)device.absSize.range);

                }

                switch (orientation) {

                    case Surface.ROTATION_90:

                        temp = scaledX;

                        scaledX = scaledY;

                        scaledY = w-temp;

                        break;

                    case Surface.ROTATION_180:

                        scaledX = w-scaledX;

                        scaledY = h-scaledY;

                        break;

                    case Surface.ROTATION_270:

                        temp = scaledX;

                        scaledX = h-scaledY;

                        scaledY = temp;

                        break;

                }

                if (scaledX == 0) {

                    edgeFlags += MotionEvent.EDGE_LEFT;

                } else if (scaledX == display.getWidth() - 1.0f) {

                    edgeFlags += MotionEvent.EDGE_RIGHT;

                }


                if (scaledY == 0) {

                    edgeFlags += MotionEvent.EDGE_TOP;

                } else if (scaledY == display.getHeight() - 1.0f) {

                    edgeFlags += MotionEvent.EDGE_BOTTOM;

                }


            } else {

                scaledX *= xMoveScale;

                scaledY *= yMoveScale;

                switch (orientation) {

                    case Surface.ROTATION_90:

                        temp = scaledX;

                        scaledX = scaledY;

                        scaledY = -temp;

                        break;

                    case Surface.ROTATION_180:

                        scaledX = -scaledX;

                        scaledY = -scaledY;

                        break;

                    case Surface.ROTATION_270:

                        temp = scaledX;

                        scaledX = -scaledY;

                        scaledY = temp;

                        break;

                }

            }


            changed = false;

            if (down != lastDown) {

                int action;

                lastDown = down;

                if (down) {

                    action = MotionEvent.ACTION_DOWN;

                    downTime = curTime;

                } else {

                    action = MotionEvent.ACTION_UP;

                }

                currentMove = null;

                if (!isAbs) {

                    x = y = 0;

                }

                return MotionEvent.obtain(downTime, curTime, action,

                        scaledX, scaledY, scaledPressure, scaledSize, metaState,

                        xPrecision, yPrecision, device.id, edgeFlags);

            } else {

                if (currentMove != null) {

                    if (false) Log.i("InputDevice", "Adding batch x=" + scaledX

                            + " y=" + scaledY + " to " + currentMove);

                    currentMove.addBatch(curTime, scaledX, scaledY,

                            scaledPressure, scaledSize, metaState);

                    if (WindowManagerPolicy.WATCH_POINTER) {

                        Log.i("KeyInputQueue", "Updating: " + currentMove);

                    }

                    return null;

                }

                MotionEvent me = MotionEvent.obtain(downTime, curTime,

                        MotionEvent.ACTION_MOVE, scaledX, scaledY,

                        scaledPressure, scaledSize, metaState,

                        xPrecision, yPrecision, device.id, edgeFlags);

                currentMove = me;

                return me;

            }

        }

    }


    static class AbsoluteInfo {

        int minValue;

        int maxValue;

        int range;

        int flat;

        int fuzz;

    };

       //once edit

    static class TransformInfo {

       float x1;

       float y1;

       float z1;

       float x2;

       float y2;

       float z2;

       float s;

    };

    //edit ends


    InputDevice(int _id, int _classes, String _name,

            AbsoluteInfo _absX, AbsoluteInfo _absY,

            AbsoluteInfo _absPressure, AbsoluteInfo _absSize) {

        id = _id;

        classes = _classes;

        name = _name;

        absX = _absX;

        absY = _absY;

        absPressure = _absPressure;

        absSize = _absSize;


        //once edit

        desFile = new File(CALIBRATION_FILE);

        readCalibrate();

        //edit ends

    }

    static void readCalibrate(){

        //xxw added

        Log.i("XXW","readCalibrate!");

        TransformInfo t = null;

        try {

            FileInputStream is = new FileInputStream(CALIBRATION_FILE);

            byte[] mBuffer = new byte[64];

            int len = is.read(mBuffer);

            is.close();


            if (len > 0) {

                int i;

                for (i = 0 ; i < len ; i++) {

                    if (mBuffer == '\n' || mBuffer == 0) {

                        break;

                    }

                }

                len = i;

            }


            StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0, len));

            t = new TransformInfo ();

            t.x1 = Integer.parseInt( st.nextToken() );

            t.y1 = Integer.parseInt( st.nextToken() );

            t.z1 = Integer.parseInt( st.nextToken() );

            t.x2 = Integer.parseInt( st.nextToken() );

            t.y2 = Integer.parseInt( st.nextToken() );

            t.z2 = Integer.parseInt( st.nextToken() );

            t.s = Integer.parseInt( st.nextToken() );

        } catch (java.io.FileNotFoundException e) {

            Log.i("XXW", "FileNotFound!");

        } catch (java.io.IOException e) {

            Log.i("XXW", "IOException");

        }

        tInfo = t;

        Log.i("XXW","readCalibrate done!");

    }

};

与上一次的那个InputDevice相比我将读取校准文件的代码单独的变成一个函数,之所以这么做是因为我们打算不重启就可以直接让android校准完成。这里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件然后将系统属性变成其他值不再读取文件。

下面我们就要写一个apk来实现校准了。

这里我尝试了2种方法一种是纯java的apk 一种是jni的apk。其实对于校准来说上层已经能拿到x,y那么我们取5个点就已经可以算出来那7个校准值然后利用java存文件了。整个apk的java代码

package com.android.calibrate;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import android.util.Log;

public class Calibrate {

    private calibration cal;

    public Calibrate() {

        cal = new calibration();

    }

    class calibration {

        int x[] = new int[5];

        int xfb[] = new int[5];

        int y[] = new int[5];

        int yfb[] = new int[5];

        int a[] = new int[7];

    };

    boolean perform_calibration() {

        Log.i("XXW", "perform_calibration");

        int j;

        float n, x, y, x2, y2, xy, z, zx, zy;

        float det, a, b, c, e, f, i;

        float scaling = (float)65536.0;

        // Get sums for matrix

        n = x = y = x2 = y2 = xy = 0;

        for (j = 0; j < 5; j++) {

            n += 1.0;

            x += (float)cal.x[j];

            y += (float)cal.y[j];

            x2 += (float)(cal.x[j] * cal.x[j]);

            y2 += (float)(cal.y[j] * cal.y[j]);

            xy += (float)(cal.x[j] * cal.y[j]);

        }

        // Get determinant of matrix -- check if determinant is too small

        det = n * (x2 * y2 - xy * xy) + x * (xy * y - x * y2) + y * (x * xy - y * x2);

        if (det < 0.1 && det > -0.1) {

            Log.i("ts_calibrate: determinant is too small -- %f\n", "" + det);

            return false;

        }

        // Get elements of inverse matrix

        a = (x2 * y2 - xy * xy) / det;

        b = (xy * y - x * y2) / det;

        c = (x * xy - y * x2) / det;

        e = (n * y2 - y * y) / det;

        f = (x * y - n * xy) / det;

        i = (n * x2 - x * x) / det;

        // Get sums for x calibration

        z = zx = zy = 0;

        for (j = 0; j < 5; j++) {

            z += (float)cal.xfb[j];

            zx += (float)(cal.xfb[j] * cal.x[j]);

            zy += (float)(cal.xfb[j] * cal.y[j]);

        }

        // Now multiply out to get the calibration for framebuffer x coord

        cal.a[0] = (int)((a * z + b * zx + c * zy) * (scaling));

        cal.a[1] = (int)((b * z + e * zx + f * zy) * (scaling));

        cal.a[2] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c

                * z + f * zx + i * zy));

        // Get sums for y calibration

        z = zx = zy = 0;

        for (j = 0; j < 5; j++) {

            z += (float)cal.yfb[j];

            zx += (float)(cal.yfb[j] * cal.x[j]);

            zy += (float)(cal.yfb[j] * cal.y[j]);

        }

        // Now multiply out to get the calibration for framebuffer y coord

        cal.a[3] = (int)((a * z + b * zx + c * zy) * (scaling));

        cal.a[4] = (int)((b * z + e * zx + f * zy) * (scaling));

        cal.a[5] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c

                * z + f * zx + i * zy));

        // If we got here, we're OK, so assign scaling to a[6] and return

        cal.a[6] = (int)scaling;

        return true;

        /*

         * // This code was here originally to just insert default values

         * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1;

         * return 1;

         */

    }

    void get_sample(int index, int x1, int y1, int x, int y) {

        Log.i("XXW", "get_sample");

        cal.x[index] = x1;

        cal.y[index] = y1;

        cal.xfb[index] = x;

        cal.yfb[index] = y;

    }

    int calibrate_main() {

        int result = 0;

        Log.i("XXW", "calibrate_main");

        if (perform_calibration()) {

            String strPara = String.format("%d %d %d %d %d %d %d", cal.a[1], cal.a[2], cal.a[0],

                    cal.a[4], cal.a[5], cal.a[3], cal.a[6]);

            boolean success = new File("/data/etc").mkdir();

            if (!success) {

                Log.i(this.toString(), "no success");

            }

            File desFile = new File("/data/etc/pointercal");

            if (!desFile.exists())

                try {

                    desFile.createNewFile();

                } catch (IOException e1) {

                    e1.printStackTrace();

                }

            FileOutputStream fos;

            try {

                fos = new FileOutputStream(desFile);

                byte[] buf = strPara.getBytes();

                int bytesRead = buf.length;

                try {

                    fos.write(buf, 0, bytesRead);

                    fos.flush();

                    fos.close();

                } catch (IOException e) {                  

                    e.printStackTrace();

                }

            } catch (FileNotFoundException e) {            

                e.printStackTrace();

            }

            result = 0;

        } else {

            result = -1;

        }

        return result;

    }

}

其实这个就是tslib里那个ts_calibrate.cpp的姐妹篇。重要的函数就一个perform_calibration() 。这里要注意的是你必须首先利用get_sample读取5个点的值来初始化cal然后再调用calibrate_main来计算校验系数。而且那5个点的顺序为左上 右上 右下 左下 中间。这4个点基本上应该在屏幕的边缘附近否则计算出来的校验值可能不准。 利用上面的那个类写一个apk出来跑跑看流程应该就可以了。

package com.android.calibrate;

import com.android.calibrate.R;

import android.app.Activity;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.os.Bundle;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.Window;

import android.view.WindowManager;

import android.view.View.OnTouchListener;

import android.widget.Toast;

import android.os.SystemProperties;

public class AndroidCalibrate extends Activity {

    final String TAG = "ScreenCalibration";

    final int UI_SCREEN_WIDTH = 800;

    final int UI_SCREEN_HEIGHT = 480;

    CrossView myview;

    int direction;

    private Calibrate cal;

    int xList[] = {

            50, UI_SCREEN_WIDTH - 50, UI_SCREEN_WIDTH - 50, 50, UI_SCREEN_WIDTH / 2

    };

    int yList[] = {

            50, UI_SCREEN_HEIGHT - 50, UI_SCREEN_HEIGHT - 50, 50, UI_SCREEN_HEIGHT / 2

    };

    static void setNotTitle(Activity act) {

        act.requestWindowFeature(Window.FEATURE_NO_TITLE);

    }

    static void setFullScreen(Activity act) {

        setNotTitle(act);

        act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

                WindowManager.LayoutParams.FLAG_FULLSCREEN);

    }

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setFullScreen(this);   

        myview = new CrossView(this);

        setContentView(myview);


        SystemProperties.set("ts.config.calibrate", "start");


        cal = new Calibrate();

        direction = 0;


        myview.setOnTouchListener(new OnTouchListener() {

            @Override

            public boolean onTouch(View v, MotionEvent event) {

                Log.i("OnTouch", event.getX() + "," + event.getY());

                v.invalidate();

                if (direction < 4) {

                    Log.i("XXW time onTouchListener", " " + direction);

                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),

                            xList[direction], yList[direction]);

                }

                if (direction == 4) {

                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),

                            xList[direction], yList[direction]);

                    Log.i("XXW", "calibrate_main");

                    cal.calibrate_main();

                    Toast.makeText(getBaseContext(), "Calibrate Done!", Toast.LENGTH_SHORT).show();

                    SystemProperties.set("ts.config.calibrate", "done");

                    AndroidCalibrate.this.finish();

                }

                direction++;

                return false;

            }

        });

    }

    public class CrossView extends View {

        public CrossView(Context context) {

            super(context);

        }

        public void onDraw(Canvas canvas) {

            Paint paint = new Paint();

            paint.setColor(Color.GREEN);

            if (direction == 0) {

                canvas.drawLine(40, 50, 60, 50, paint);

                canvas.drawLine(50, 40, 50, 60, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 1) {

                canvas.drawLine(UI_SCREEN_WIDTH - 60, 50, UI_SCREEN_WIDTH - 40, 50, paint);

                canvas.drawLine(UI_SCREEN_WIDTH - 50, 40, UI_SCREEN_WIDTH - 50, 60, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 2) {

                canvas.drawLine(UI_SCREEN_WIDTH - 60, UI_SCREEN_HEIGHT - 50, UI_SCREEN_WIDTH - 40,

                        UI_SCREEN_HEIGHT - 50, paint);

                canvas.drawLine(UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 60, UI_SCREEN_WIDTH - 50,

                        UI_SCREEN_HEIGHT - 40, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 3) {

                canvas.drawLine(40, UI_SCREEN_HEIGHT - 50, 60, UI_SCREEN_HEIGHT - 50, paint);

                canvas.drawLine(50, UI_SCREEN_HEIGHT - 60, 50, UI_SCREEN_HEIGHT - 40, paint);

                paint.setColor(Color.WHITE);

            } else if (direction == 4) {

                canvas.drawLine(UI_SCREEN_WIDTH / 2 - 10, UI_SCREEN_HEIGHT / 2,

                        UI_SCREEN_WIDTH / 2 + 10, UI_SCREEN_HEIGHT / 2, paint);

                canvas.drawLine(UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 - 10,

                        UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 + 10, paint);

                paint.setColor(Color.WHITE);

            } else {

            }

            // canvas.drawText(getResources().getString(R.string.

            // screen_calibration_content),

            // UI_SCREEN_WIDTH / 2 - 50, UI_SCREEN_HEIGHT / 2, paint);

            super.onDraw(canvas);

        }

    }

}

如果对linux,Android,wince 等嵌入式底层有兴趣的,请加这个QQ群吧,群号:27100460
点赞  2013-6-18 10:08
大侠请问,你的android.mk只有一个还是多个了,你的adnroid系统是哪个版本的,适合所有android系统的吗?刚学,多多指教
点赞  2013-10-24 17:15

回复 5楼wuzhihuawuzhihu 的帖子

mk 文件很多的啊。android2.3 和4.0 了。是吧,嘿嘿。
点赞  2013-10-24 22:28
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复