Android 增加 Happy Touch 触摸屏支持

1、在hid-ids.h中加入vid pid

#define USB_VENDOR_ID_HAPPYTOUCH	0x0416
#define USB_DEVICE_ID_HAPPYTOUCH_SCREEN	0x5030

2、在hid-multitouch.c->mt_devices[] 中加入

{ .driver_data = MT_CLS_DEFAULT,
HID_USB_DEVICE(USB_VENDOR_ID_HAPPYTOUCH,
USB_DEVICE_ID_HAPPYTOUCH_SCREEN) }

3、在hid-core.c->hid_have_special_driver[]中加入HID_USB_DEVICE(VID,PID)

{ HID_USB_DEVICE(USB_VENDOR_ID_HAPPYTOUCH,USB_DEVICE_ID_HAPPYTOUCH_SCREEN)}

4、设置kernel编译变量

$ export ARCH=arm
$ export SUBARCH=arm
$ export CROSS_COMPILE=arm-eabi-

5、在内核的config文件中,确认驱动中是否添加了hid-multitouch模块

运行make menuconfig后,将HID Multitouch panels选上。

$ make clear
$ make mrproper
$ make menuconfig
Device Drivers --->
HID Devices --->
Special HID Drivers --->
HID Multitouch panels

6、编译hid-multitouch.ko

$ make prepare
$ make scripts
$ make M=drivers/hid

关于如何单独编译kernel驱动的方法,请点击这里

7、设置触摸屏

Android触摸屏设置方法请点击 这里

为Android APP添加最终用户许可协议

对一个android app来说,加入EULA已经是一个必不可少的内容了。下面将演示如何创建一个通用的Eula类。任何一个app都可以使用Eula.show()来显示自己的EULA,也可以通过实现Eula.OnEulaAgreedTo接口做进一步处理。

Eula.java

/**
最终用户协议文件名由ASSERT_EULA定义,存放位置为项目的assets目录
*/
public class Eula {
	private static final String ASSERT_EULA = "EULA";
	private static final String PREFERENCE_EULA_ACCEPTED = "eula.accepted";
	private static final String PREFERENCE_EULA = "eula";
	/**
        当用户选择同意后的回调函数。
        */
	static interface OnEulaAgreedTo {
		void onEulaAgreedTo();
	}
	
	private static void accept(SharedPreferences preferences) {
		preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true).commit();
	}
	
	private static void refuse(Activity activity) {
		activity.finish();
	}
	
	private static void closeStream(Closeable stream) {
		if(null != stream) {
			try {
				stream.close();
			} catch(IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private static CharSequence readEula(Activity activity) {
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(activity.getAssets().open(ASSERT_EULA)));
			String line;
			StringBuilder buffer = new StringBuilder();
			while((line = in.readLine()) != null) {
				buffer.append(line).append("\n");
			}
			return buffer;
		} catch(IOException e) {
			e.printStackTrace();
			return "";
		} finally {
			closeStream(in);
		}
	}
	
	static boolean show(final Activity activity) {
		final SharedPreferences preferences = activity.getSharedPreferences(PREFERENCE_EULA, Activity.MODE_PRIVATE);
		
		if(!preferences.getBoolean(PREFERENCE_EULA_ACCEPTED, false)) {
			final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
			builder.setTitle(R.string.eula_title);
			builder.setCancelable(true);
			builder.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
				
				@Override
				public void onClick(DialogInterface dialog, int which) {
					accept(preferences);
					if(activity instanceof OnEulaAgreedTo) {
						((OnEulaAgreedTo)activity).onEulaAgreedTo();
					}
				}
			});
			builder.setNegativeButton(R.string.eula_refuse, new DialogInterface.OnClickListener() {
				
				@Override
				public void onClick(DialogInterface dialog, int which) {
					refuse(activity);
				}
			});
			builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
				
				@Override
				public void onCancel(DialogInterface dialog) {
					refuse(activity);
				}
			});
			builder.setMessage(readEula(activity));
			builder.create().show();
			return false;
		}
		return true;
	}
}

使用上面的EULA类: 测试的时候,你可能需要删除或者修改/data/目录下app对应的eula.xml文件,因为这里把用户的授权信息存放在了SharedPreferences中,而这是个永久存储的方法。 EulaExample.java

public class EulaExample extends Activity implements Eula.OnEulaAgreedTo {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_eula_example);
		Eula.show(this);
	}

	@Override
	public void onEulaAgreedTo() {
		Toast.makeText(this, "Think you !", Toast.LENGTH_SHORT).show();
	}
}

Android音频之SoundPool

Android提供了两种不同的框架来处理音频,分别是:

  • MediaPlayer / MediaRecoder:处理音频的标准方法,但数据源必须是文件或者基于流的数据。使用这个类处理音频文件的时候须有创建自己的线程运行。本文要说的SoundPool类就是使用了这个框架。
  • AudioTrack / AudioRecoder:该框架支持直接访问原始音频文件。用于在内存中处理音频文件,或者开始播放音频的同时写入缓冲区,或者在其他不需要文件和数据流的场合中使用。运行过程中不需要创建新线程。

SoundPool

和MediaPlayer主要用来播放一些长音频不同,SoundPool一般用来处理一些短的但是带有重叠,回放等特效的音频。这是因为SoundPool带有一个音频缓冲区,可以很方便的支持音频的回放、快放和慢放。

使用SoundPool的基本步骤

  • 初始化
  • 加载资源
  • 播放
  • 释放资源

测试SoundPool

我在测试过程中发现不能播放大于1M的音频文件,所以这里使用一个900K的sound.wav做测试。在点击Play后,音乐将重复播放5次(mySP.play(soundId, 1f, 1f, 1, 5, rate)中的第5个参数指定),每重复点击Play按钮,音乐会降低一半速率播放(mySP.play(soundId, 1f, 1f, 1, 5, rate)中的第6个参数指定)。测试程序同时最大可以叠加10个音频(SoundPool(10, AudioManager.STREAM_MUSIC, 0)中的第1个参数指定)。

public class AudioExamplesSP extends Activity {

	static float rate = 2f;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		Button playButton = (Button) findViewById(R.id.play_pause);
		final SoundPool mySP = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
		final int soundId = mySP.load(this, R.raw.sound, 1);

		playButton.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				rate = rate / 2;
				mySP.play(soundId, 1f, 1f, 1, 5, rate);
			}
		});
	}
}

可能的问题

如果在播放中出现下面的错误:

sample 1 not READY

原因是SoundPool需要自己在后台启动一个线程来做音频回放,当调用SoundPool.load()加载资源后立即调用SoundPool.play()进行播放的时候,有可能出现资源加载未完成的情况,在这种情况下就可能出现上面的错误。解决的方法如下:

  • 把启动播放的事件跟一个用户事件关联起来。如上面的例子。
  • 重复测试Sound.play()的返回值,例如:
int sid = 0;
for(int i = 0; i < 1000; i++) {
    if(sid == 0) {
	try {
	    rate = rate / 2;
	    sid = mySP.play(soundId, 1f, 1f, 1, 5, rate);
	    Thread.sleep(1);
	} catch(InterruptedException e) {
	    e.printStackTrace();
	}
    } else {
	    break;
    }
}

为SoundPool设置OnLoadCompleteListener:这个监听器需要Android2.2以上才能使用。例如:

mySP.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
			
	@Override
	public void onLoadComplete(SoundPool arg0, int arg1, int arg2) {
		mySP.play(soundId, 1f, 1f, 1, 5, rate);
	}
});

ubuntu下编译ImageMagick使支持JPEG

OS : ubuntu 12.04 (32bit)

ImageMagick ver : 6.8.4

ubuntu 12.04默认使用的是JPEG-8,但是ImageMagick-6.8.4需要JPEG-9。使用默认configure编译ImageMagick后,每当需要处理jpeg文件时,IMageMagick报错:

Magick: Wrong JPEG library version: library is 80, caller expects 90

解决方法如下:

  • 下载编译安装JPEG-9,下载地址http://www.ijg.org/ 。编译并安装到默认位置:/usr/local/

  • 使用下面的配置,重新编译ImageMagick-6.8.4,并且安装

    $ ./configure CXXFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib --disable-static --with-quantum-depth=8
    

进入/usr/local/lib/下,查看一下libMagickCore-6.Q8.so的依赖库:

$ ldd libMagickCore-6.Q8.so  | grep -i  jpeg

输出:

	libjpeg.so.9 => /usr/local/lib/libjpeg.so.9 (0xb711f000)
	libjpeg.so.8 => /usr/lib/i386-linux-gnu/libjpeg.so.8 (0xb67cb000)

结果显示ImageMagick已经正确连接到jpeg-9。

Done !

Android使用多点触摸(一)

Android触摸事件分类

事件名 动作
ACTION_DOWN 按下第一个点
ACTION_POINTER_DOWN 按下第二个点
ACTION_MOVE 手势发生移动
ACTION_POINTER_UP 释放第二个点
ACTION_UP 释放第一个点

简单示例

activity_multitouch.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".Multitouch" >

	<ImageView android:id="@+id/imageView"
	    android:contentDescription="@string/DESC"
	    android:layout_width="fill_parent"
	    android:layout_height="fill_parent"
	    android:src="@drawable/aywdhz8u"
	    android:scaleType="matrix" >
	</ImageView>
</RelativeLayout>

Multitouch.java

public class Multitouch extends Activity implements OnTouchListener{

	Matrix matrix = new Matrix();
	Matrix eventMatrix = new Matrix();

	final static int NONE = 0;
	final static int DRAG = 1;
	final static int ZOOM = 2;
	int touchState = NONE;

	final static int MIN_DIST = 50;
	static float eventDistance = 0;
	static float centerX = 0, centerY = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_multitouch);

		ImageView view = (ImageView)findViewById(R.id.imageView);
		view.setOnTouchListener(this);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.multitouch, menu);
		return true;
	}

	@Override
	public boolean onTouch(View arg0, MotionEvent arg1) {
		ImageView view = (ImageView)arg0;

		switch(arg1.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			touchState = DRAG;
			centerX = arg1.getX(0);
			centerY = arg1.getY(0);
			eventMatrix.set(matrix);
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
			eventDistance = calcDistance(arg1);
			calcMidpoint(centerX, centerY, arg1);
			if(eventDistance > MIN_DIST) {
				eventMatrix.set(matrix);
				touchState = ZOOM;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			if(touchState == DRAG) {
				matrix.set(eventMatrix);
				matrix.setTranslate(arg1.getX(0) - centerX, arg1.getY(0) - centerY);
			} else if(touchState == ZOOM) {
				float dist = calcDistance(arg1);
				if(dist > MIN_DIST) {
					matrix.set(eventMatrix);
					float scale = dist / eventDistance;
					matrix.postScale(scale, scale, centerX, centerY);
				}
			}
			view.setImageMatrix(matrix);
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			touchState = NONE;
			break;
		}
		return true;
	}

	private float calcDistance(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return (float) Math.sqrt(x * x + y * y);
	}

	private void calcMidpoint(float centerX, float centerY, MotionEvent event) {
		centerX = (event.getX(0) + event.getX(1)) / 2;
		centerY = (event.getY(0) + event.getY(1)) / 2;
	}
}

ref:《Android开发秘籍》