您的位置:首页 > 移动开发 > Android开发

android_模拟按键资料

2011-08-24 12:11 253 查看
1 键盘监控分析

Android 的按键产生的是一个 KeyEvent ,这个 KeyEvent 只能被最上层 focus 窗口的 activity 和 view 得到。

所有的按键事件都会首先触发 public boolean dispatchKeyEvent(KeyEvent event) 这个函数,这个函数在 SDK 里的英文说明如下:

boolean zy.keytest.keytest.dispatchKeyEvent(KeyEvent event)

Overrides: dispatchKeyEvent(...) in Activity

public boolean dispatchKeyEvent (KeyEvent event)

Since: API Level 1

Called to process key events. You can override this to intercept all key events before they are dispatched to the window. Be sure to call this implementation for key events that should be handled normally.

Parameters

event The key event.

Returns

boolean Return true if this event was consumed.

然后还会触发 public void onUserInteraction() 这个函数,这个函数的说明如下:

void zy.keytest.keytest.onUserInteraction()

Overrides: onUserInteraction() in Activity

public void onUserInteraction ()

Since: API Level 3

Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and onUserLeaveHint() are intended
to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.

All calls to your activity's onUserLeaveHint() callback will be accompanied by calls to onUserInteraction(). This ensures that your activity will be told of relevant user activity such as pulling down the notification pane and touching an item there.

Note that this callback will be invoked for the touch down action that begins a touch gesture, but may not be invoked for the touch-moved and touch-up actions that follow.

See Also

onUserLeaveHint()

按下接下来触发 public boolean onKeyDown(int keyCode, KeyEvent event) 这个函数,一般相应按下都是重载这个函数。

详细的流程如下:

当鼠标键按下时(即触摸):

首先触发 dispatchTouchEvent ,然后触发 onUserInteraction ,再次 onTouchEvent 。如果是点击的话,紧跟着下列事件(点击分俩步, ACTION_DOWN,ACTION_up) ,触发 dispatchTouchEvent ,再次 onTouchEvent ,当 ACTION_up 事件时不会触发 onUserInteraction (可查看源代码)

当键盘按下时:

首先触发 dispatchKeyEvent ,然后触发 onUserInteraction ,再次 onKeyDown ,如果按下紧接着松开,则是俩步,紧跟着触发 dispatchKeyEvent ,然后触发 onUserInteraction , 再次 onKeyUp ,注意与触摸不同,当松开按键时 onUserInteraction 也会触发。

而通过继承 InputMethodService 类重写这个类里面的 public boolean onKeyDown(int keyCode, KeyEvent event) 函数,则可以监听键盘按键, SDK 里对这个函数有详细的描述。

boolean com.example.android.softkeyboard.SoftKeyboard.onKeyDown(int keyCode, KeyEvent event)

Use this to monitor key events being delivered to the application. We get first crack at them, and can either resume them or let them continue to the app.

Overrides: onKeyDown(...) in InputMethodService

Parameters:

keyCode

event

这个函数能够在应用程序得到按键之前,输入法先得到这个按键,并且可以释放他们或者继续传递给应用程序。

结论: Android 的按键产生的是一个 KeyEvent ,这个 KeyEvent 只能被最上层获得焦点窗口的 activity 和 view 得到,无法被其他进程获得,进程间的键盘监听是无法实现的。

2 键盘模拟分析

键盘模拟这一块在调研中,在 SDK1.6 版本以前有一种方法,使用 android.view.IWindowManager ,其实现的源代码如下:

package org.anddev.android.simualtekeys;

import android.app.Activity;

import android.os.Bundle;

import android.os.DeadObjectException;

import android.os.ServiceManager;

import android.view.IWindowManager;

import android.view.KeyEvent;

import android.view.Menu;

import android.view.View;

import android.view.View.OnClickListener;

public class SimualteKeyInput extends Activity {

/* The WindowManager capable of injecting keyStrokes. */

final IWindowManager windowManager = IWindowManager.Stub

.asInterface(ServiceManager.getService("window"));

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

setContentView(R.layout.main);

/* Make the button do the menu-popup. */

this.findViewById(R.id.cmd_simulate_key).setOnClickListener(

new OnClickListener() {

@Override

public void onClick(View arg0) {

/* Start the key-simulation in a thread

* so we do not block the GUI. */

new Thread(new Runnable() {

public void run() {

/* Simulate a KeyStroke to the menu-button. */

simulateKeystroke(KeyEvent.KEYCODE_SOFT_LEFT);

}

}).start(); /* And start the Thread. */

}

});

}

/** Create a dummy-menu. */

@Override

public boolean onCreateOptionsMenu(Menu menu) {

boolean supRetVal = super.onCreateOptionsMenu(menu);

menu.add(0, 0, "Awesome it works =)");

return supRetVal;

}

/** Wrapper-function taking a KeyCode.

* A complete KeyStroke is DOWN and UP Action on a key! */

private void simulateKeystroke(int KeyCode) {

doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyCode));

doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyCode));

}

/** This function actually handles the KeyStroke-Injection. */

private void doInjectKeyEvent(KeyEvent kEvent) {

try {

/* Inject the KeyEvent to the Window-Manager. */

windowManager.injectKeyEvent(kEvent.isDown(), kEvent.getKeyCode(),

kEvent.getRepeatCount(), kEvent.getDownTime(), kEvent

.getEventTime(), true);

} catch (DeadObjectException e) {

e.printStackTrace();

}

}

}

Main.xml 文件如下

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

androidrientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<Button id="@+id/cmd_simulate_key"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="Simulate Menu Key Press"

/>

</LinearLayout>

这个方法在 1.6 版本以上的 sdk 里没法实行,因为没有 android.os.ServiceManager 和

android.view.IWindowManager 这两个 package 。 在 framework 调用 IWindowManager 就是在 Android 的全部源码的环境下利用 Android.mk 文件写程序,这样的好处是可以调用 sdk 里没有的库包,比如 IWindowManager ,编译生成的是一个 jar 文件,你可以给他运行的附加参数,比如 autotest -touch 123.0 123.0 来实现点击( 123 , 123 ),因为 Android 系统不能直接运行 jar
文件,所以你需要利用 aporcess 来调用 jar 。

第二种方法也是利用输入法的按键模拟来实现。输入法里实现按键模拟的具体源代码如下:

getCurrentInputConnection().sendKeyEvent(

new KeyEvent(KeyEvent. ACTION_DOWN , keyEventCode));

getCurrentInputConnection().sendKeyEvent(

new KeyEvent(KeyEvent. ACTION_UP , keyEventCode));

这样就模拟了一个按键的按下和弹起。

结论:输入法可以做到按键模拟,在使用模拟键盘的函数需要继承输入法的相关类,需要用户手动设置输入法(输入法程序本身不能设置自己为默认输入法),才能在自己的输入法程序里实现监听键盘,这种应用性不能广泛。普通的应用程序没法做到按键模拟。设置输入法的 api 需要 android.uid.system ,这种 uid 需要系统级签名才能使用,经过测试,可以通过自己的程序进行设置输入法,但也需要重新签名。

3 使用 Test Instrument 模拟按键

做了一个测试工程模拟按键的 demo

测试过程

首先 创建一个项目 TestApp

参数如下

Package Name: com.android.testapp

Activity Name: MainActivity

Application Name: TestApp

MainActivity 源码如下

package com.android.testapp;


其实这个只是一个被测试程序,跟模拟按键没有太大关系

然后 新建一个 Source Folder ,名为 test ,并在里面新建了包 com.android.testapp.test 。并定义了一个 TestCase ,名为 TestMainActivity ,源代码如下:

package com.android.testapp.test;

import com.android.testapp.MainActivity;

import android.app.Instrumentation;

import android.content.ContentResolver;

import android.test.ActivityInstrumentationTestCase ;

import android.test.suitebuilder.annotation.MediumTest;

import android.util.Log;

import android.view.KeyEvent;

public class TestMainActivity extends ActivityInstrumentationTestCase <MainActivity> {

private Instrumentation mInst = null ;

private ContentResolver mContentResolver = null ;

public TestMainActivity() {

super ( "com.android.testapp" , MainActivity. class );

}

public TestMainActivity(String pkg, Class<MainActivity> activityClass) {

super (pkg, activityClass);

}

@Override

protected void setUp() throws Exception {

super .setUp() ;

mInst = getInstrumentation();

mContentResolver = mInst .getContext().getContentResolver();

Log.i ( "test" , "setup" );

}

public void testStartActivity() throws Exception {

//launch activity

/* Intent intent = new Intent(Intent.ACTION_MAIN);

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

String activityPackagePath = "com.android.";

ntent.setClassName(activityPackagePath, TargetActivity.getClass().getName());

Activity mActivity = (TargetActivity) getInstrumentation().startActivitySync(intent);

mInst.waitForIdleSync();*/

//send keyevent to press button

mInst .sendCharacterSync(KeyEvent. KEYCODE_1 );

Log.i ( "test" , "send key 8" );

mInst .waitForIdleSync();

}

@MediumTest

public void testSum() {

assertEquals (3, getActivity() .sum(1, 2));

mInst .sendCharacterSync(KeyEvent. KEYCODE_0 );

Log.i ( "test" , "send key 7" );

mInst .waitForIdleSync();

}

@MediumTest

public void testSubstract() {

assertEquals (-1, getActivity() .substract(1, 2));

}

}

测试的时候将会执行几个测试函数,我在里面加上了模拟按键的代码,并通过日志打印,并且通过输入法程序的日志来捕获

Manifest 文件源代码

<?xml version="1.0" encoding="utf-8" ?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.android.testapp"

android:versionCode="1"

android:versionName="1.0.0" >

<application android:icon="@drawable/icon" android:label="@string/app_name" >

<activity android:name=".MainActivity"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<uses-library android:name="android.test.runner" />

</application>

<instrumentation android:targetPackage="com.android.testapp" android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests" ></nstrumentation>

</manifest>

运行测试过程:

首先设置 TestApp 的 Run configueration

设置为 do nothing 不然我们运行就会启动我们的主程序

然后运行我们的工程,将工程安装到模拟器上

然后打开命令行,运行命令

adb shell pm list packages

可以看到我们的工程已经安装在模拟器上

然后运行命令

adb shell am instrument -e class com.android.testapp.test.TestMainActivity –w com.android.testapp/android.test.InstrumentationTestRunner

复制粘贴会多有问题 直接用键盘敲入 如图

测试执行完毕,查看 logcat 可以看到结果

通过日志可以看到输入法截获到了我们测试程序发出的按键

查看了系统 test Instrumentation 源码

Instrumentation 类里面关于 " sendKeySync " 一类函数的实现

ublic void sendKeySync(KeyEvent event) {

validateNotAppThread();

try {

(IWindowManager.Stub.asInterface(ServiceManager.getService("window" )))

.injectKeyEvent(event, true );

} catch (RemoteException e) {

}

}

采用的 IWindowManager 类里的injectKeyEvent 函数, 之前已经调研了 IWindowManager 类在1.6 以后的版本之后被隐藏掉了

injectKeyEvent 函数网上有人说得在源码环境下编译可以使用。
http://blog.csdn.net/zcpangzi/archive/2010/03/15/5383306.aspx
所以,我们考虑利用程序在命令行下进行键盘模拟

我们新建一个工程 runcmd

源代码为

package zy.runcmd;

import java.io.IOException;

import android.app.Activity;

mport android.os.Bundle;

import android.util.Log;

public class runcmd extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

try {

Runtime.getRuntime().exec("am instrument -e class com.android.testapp.test.TestMainActivity -w com.android.testapp/android.test.InstrumentationTestRunner");

Log.i("run","success!!!!!!!!!");

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

Log.i("run",e.toString());

}

}

}

执行成功之后 log 里会打印 success!!!!

并且可以在 log 里看到相应的效果

我们运行此程序之后

查看 logcat

可以看到 test 程序被执行了。

并且发出的按键 7 8 都被我们的输入法程序得到了。

4 injectKeyEvent 跨进程传递

injectKeyEvent 函数跨进程进行按键模拟会出现错误: 07-25 09:39:31.901: WARN/WindowManager(55): Permission denied: injecting key event from pid 460 uid 10037 to window Window{43e0fe80 com.android.contacts/com.android.contacts.DialtactsActivity paused=false} owned by uid 10001

权限不够。即使再加上

<uses-permission android:name="android.permission.INJECT_EVENTS" />

也依然不行。

结论: Test Instruments 底层调用的 injectKeyEvent 函数, Test Instruments 环境下, injectKeyEvent 函数只能给被测试的activity 传递模拟的按键,而 Test Instruments 的工程在测试时,会调出被测试的 activity ,传递模拟按键,在跨进程传递时则会出现 Permission denied ,即使加上权限声明也依然不行。推测可能是系统级进程和同一进程下才能使用这个API 进行按键传递。

5 把自己的 APK 放到目标程序的进程中运行,使用 injectKeyEvent 函数

我们测试使用的 android.uid.phone ,也就是拨号程序,希望模拟一个按键到拨号程序里。于是在我们的 Manifest.xml 文件里添加 android:sharedUserId="android.uid.phone" ,这时候我们在模拟器或者手机上再次安装,会提示安装错误,签名不对。于是我们可以用系统签名的文件对我们的 APK 程序进行重新签名。

首先用 Eclipse 编译出我们的 apk ,这个 apk 是不能安装的。

用 winrar 打开我们的 apk 。

删掉 META-INF 下面的两个签名文件。

然后用 signapk.jar 工具重新签名

首先找到密钥文件,在我的 Android 源码目录中的位置

是 "android2.0\build\target\product\security" ,下面的 platform.pk8 和 platform.x509.pem 两个文件。

然后用 Android 提供的 Signapk 工具来签名,我写了一个 bat 文件

@ECHO OFF

Echo Auto-sign Created By Dave Da illest 1

Echo Update.apk is now being signed and will be renamed to updated.apk

ava -jar signapk.jar platform.x509.pem platform.pk8 update.apk updated.apk

Echo Signing Complete

Pause

EXIT

然后将我们删掉两个签名文件的 apk 拷贝到签名工具目录下,修改名字为 update.apk ,然后使用这个 bat 文件。会生成 updated.apk 。我们用 winrar 打开这个 updated.apk 文件。

可以看到已经重新签名完成。

将 updated.apk 安装到手机上。

然后经过我们测试,能够在打电话的界面上模拟按键。在模拟器和 G2 手机上都可以实现。

这也有一个问题,就是这样生成的程序只有在原始的 Android 系统或者是自己编译的系统中才可以用,因为这样的系统才可以拿到 platform.pk8 和 platform.x509.pem 两个文件。要是别家公司做的 Android 上连安装都安装不了。

我们测试的环境是 HTC G2 手机,然后在 MOTO 的 MILESTONE 手机上不能安装,应该是签名的 key 不一致。

结论:由于采用 injectKeyEvent 不能跨进程,只能放到被模拟按键进程中使用被模拟按键密钥签名后才可以使用。由此可见,要实现在别人程序中发送模拟键盘按键,需要我们的程序跟别人用相同的签名,然后使用同一个 shareduserid 即可。如果要在系统的程序中模拟按键,我们只需要获得系统的签名的
key 进行签名。不同的厂商的 Android 系统有不同的 key 。只要获得这个 key 就能够在相对应的程序里进行按键模拟
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: