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;

import android.app.Activity;

import android.os.Bundle;

public class MainActivity extends Activity {

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

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

public int sum(int a,int b)

{

return a+b;

}

public int substract(int a,int b)

{

return a-b;

}

}

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

然后 新建一个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.";

intent.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"></instrumentation>

</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 "一类函数的实现

public 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;

import 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

java -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就能够在相对应的程序里进行按键模拟。

Android模拟键盘和键盘监听相关推荐

  1. Android 键盘监听 键盘展开/收起监听

    如和优雅地实现键盘展开/收起监听 Android 键盘的展开与收起一直是个很遭人诟病的东西 方法各式各样都有,但是基本都不是很好用,有各种限制 这里编写了一个优雅地实现方式,利用屏幕实际高度与显示区域 ...

  2. js对手机键盘事件的监听

    js还没有办法对手机软键盘直接进行监听的,但是可以有其他角度来判断软键盘是否弹起.比如输入框是否获取焦点等. focusin和focusout支持冒泡,对应focus和blur, 使用focusin和 ...

  3. vue用户行为收集_vue 实现移动端键盘搜索事件监听

    1.首先注意,input的type="serch" 2.监听keypress事件 (1)KeyDown.KeyUp 事件 这些事件是当一个对象具有焦点时按下 ( KeyDown ) ...

  4. ionic3开发系列——实现对手机软件键盘按键的监听

    最近开发项目,上级boss给出的一个需求是:在input输入框输入值后,点击手机软件键盘的确认键之后对文本框中的值进行下一步的操作. 百度了好多都没有找到关于ionic3相关的教程或者解决办法, 看到 ...

  5. android 监听物理返回键,Android应用开发之react-native 监听Android物理返回键

    本文将带你了解Android应用开发之react-native 监听Android物理返回键,希望本文对大家学Android有所帮助. 1. componentWillMount(){         ...

  6. android 窗口监听按键,Android编程实现Dialog窗体监听的方法

    本文实例讲述了Android编程实现Dialog窗体监听的方法.分享给大家供大家参考,具体如下: 今天做了一个Dialong窗体监听包括窗体内的xml监听. 效果图: test.class代码 pac ...

  7. android 关机消息广播,Android开机、关机播音监听

    Android开机.关机广播监听 一.开机广播的监听 1.在AndroidManifest.xml文件注册接收开机广播 2.在BroadcastReceiver里执行要做的事情 public clas ...

  8. Android USB摄像头插拔监听

    Android USB摄像头插拔监听 一.简要介绍 二.使用广播监听USB摄像头插拔情况 1.注册监听USB设备插拔广播 2.判断UsbDevice是否是Camera设备 (重点) 二.通过UsbMa ...

  9. Android DownloadManager下载完成事件监听(系列4)

     Android DownloadManager下载完成事件监听(系列4) 我在之前写了写一些关于Android DownloadManager 的文章: (系列1)<Android大数据. ...

  10. Android实现双击事件的监听

    本文转自:Android实现双击事件的监听 Android实现view的双击事件监听,一般使用的场景,列表双击顶部回到第一条! 双击监听 /*** view双击监听事件*/ public class ...

最新文章

  1. 计算图像的标准差与方差(meanStdDev() 函数)
  2. SSH Secure Shell Client无法连接utbuntu的报错
  3. python生成随机密码
  4. XCTF(攻防世界)—进阶web题Write Up(二)
  5. 彻底卸载oracle
  6. C/C++中const关键字的用法及其与宏常量的比较
  7. 一统江湖的大前端(2)—— Mock.js + Node.js 如何与后端潇洒分手
  8. Python使用更相减损术计算两个整数的最大公约数
  9. windows hosts 作用以及常见问题解决
  10. ajax 弹框 示例,AJAX实现鼠标经过弹出详细介绍示例
  11. saiku 展示优化
  12. python生成10000个样本数据集_python – 为什么随机抽样比例数据集不是样本量? (pandas .sample()示例)...
  13. 关于浏览器兼容的问题
  14. MTK 三星处理器一览表
  15. retinex(SSR,MSR,MSRCR)的优缺点
  16. SQL SERVER数据库日常使用总结
  17. 线性表的链式存储-单链表,从认识到实践
  18. 如何根据经纬度查询地理位置
  19. python中array是什么意思_python array是什么
  20. vue中printJS打印表格---json

热门文章

  1. 《新学期,新FLAG》突破极限
  2. 第十四周 程序阅读,理解虚函数与纯虚函数
  3. html css学习笔记-href 表格 字体 轮郭
  4. ubuntu解决中文乱码
  5. 全网最详细的2-3树图解
  6. 解决Excel打开空白页问题
  7. 身体是革命的本钱,保护颈椎人人有责!【推荐:冯天有-双手推出中医神奇】...
  8. 智能路由器操作系统openwrt 之三 音频开发
  9. dispose 模式 java_C# Dispose模式详细分析
  10. Apache POI 读写 Excel file (.xls or .xlsx)