一,通过AccessibilityService实现
简介
AccessibilityService其实是一个Servic,是其子类;设计AccessibilityService的初衷在于帮助残障用户使用android设备和应用,在后台运行,可以监听用户界面的一些状态转换,例如页面切换、焦点改变、通知、Toast,自动安装APP等模拟操作(你能做的他都能),辅助用户操作、发音(本意是这样的)等,并在触发AccessibilityEvents时由系统接收回调。后来被开发者另辟蹊径,用于一些插件开发,比如微信红包助手,还有一些需要监听第三方应用的插件。
1,能模拟的物理按键有
AccessibilityService.GLOBAL_ACTION_POWER_DIALOG 重启和关机
GLOBAL_ACTION_NOTIFICATIONS 下拉通知栏
GLOBAL_ACTION_QUICK_SETTINGS 打开快速设置
GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN 分屏
GLOBAL_ACTION_LOCK_SCREEN 锁屏----无效
GLOBAL_ACTION_KEYCODE_HEADSETHOOK 截屏 ---无效
GLOBAL_ACTION_BACK 返回
GLOBAL_ACTION_HOME 桌面
GLOBAL_ACTION_RECENTS 最近任务
2,具体实现
2.1,新建服务继承AccessibilityService,名字自定义比如我的:KeyService,如下是服务的所有代码:
package com.example.test;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
public class KeyService extends AccessibilityService {
private static final String TAG = KeyService.class.getSimpleName();
private Instrumentation instrumentation = new Instrumentation();
public static KeyService mService;
//实现辅助功能
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
mService = this;
Toast.makeText(this, "模拟物理按键锁定中...",Toast.LENGTH_LONG).show();
// setAccessibilityServiceInfo();
}
//配置需要监听的事件类型、要监听哪个程序,最小监听间隔等属性 方式二
private void setAccessibilityServiceInfo() {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
info.packageNames = new String[]{"com.tencent.mm"};
info.notificationTimeout = 100;
setServiceInfo(info);
}
@Override
public void onInterrupt() {
Toast.makeText(this, "模拟物理按键功能被迫中断", Toast.LENGTH_LONG).show();
mService = null;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this,"模拟物理按键功能已关闭", Toast.LENGTH_LONG).show();
mService = null;
}
@Override
public void onCreate() {
super.onCreate();
WindowManager windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
mParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;// 焦点
mParams.gravity = Gravity.BOTTOM;
//8.0以上系统使用WindowManager.LayoutParams.TYPE_PHONE 报错崩溃问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.format = PixelFormat.TRANSLUCENT;
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
Button recentBtn = new Button(this);
recentBtn.setBackgroundColor(Color.TRANSPARENT);
recentBtn.setText("最近任务");
recentBtn.setTextColor(Color.BLUE);
recentBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.v(TAG, "recentBtn============== ");
performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
}
});
Button homeBtn = new Button(this);
homeBtn.setBackgroundColor(Color.TRANSPARENT);
homeBtn.setText("桌面");
homeBtn.setTextColor(Color.BLUE);
homeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "点击桌面");
//GLOBAL_ACTION_POWER_DIALOG 重启和关机
//GLOBAL_ACTION_NOTIFICATIONS 下拉通知栏
//GLOBAL_ACTION_QUICK_SETTINGS 打开快速设置
//GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN 分屏
//GLOBAL_ACTION_LOCK_SCREEN 锁屏----无效
//GLOBAL_ACTION_KEYCODE_HEADSETHOOK 截屏 ---无效
//GLOBAL_ACTION_BACK 返回
//GLOBAL_ACTION_HOME 桌面
// GLOBAL_ACTION_RECENTS 最近任务
performGlobalAction(GLOBAL_ACTION_HOME);
}
});
Button backBtn = new Button(this);
backBtn.setBackgroundColor(Color.TRANSPARENT);
backBtn.setText("返回");
backBtn.setTextColor(Color.BLUE);
backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "点击返回");
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
});
linearLayout.addView(homeBtn);
linearLayout.addView(recentBtn);
linearLayout.addView(backBtn);
windowManager.addView(linearLayout, mParams);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
public static boolean isAccessibilitySettingsOn(Context mContext, Class<? extends AccessibilityService> clazz) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + clazz.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}
public static boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + KeyService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILITY IS ENABLED***");
String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
Log.v(TAG, "accessibilityService :: " + accessibilityService + " " + service);
if (accessibilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
}
return false;
}
}
2.2,既然本质上还是服务,那就要在AndroidManifest注册
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"是为了确保只有系统可以绑定该服务。
<service
android:name=".KeyService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
//配置需要监听的事件类型、要监听哪个程序,最小监听间隔等属性
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/keyattribute"/>
</service>
2.3,新建keyattribute.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask|typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric|feedbackAllMask"
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
android:canRetrieveWindowContent="true"
android:description="@string/simulate_key_description"
android:notificationTimeout="100"
android:canPerformGestures="true"/>
accessibilityEventTypes:响应的事件类型(单击、长按、滑动、通知等),这里当然是全部事件
accessibilityFeedbackType:回显给用户的方式(例如:配置TTS引擎,实现发音),辅助嘛...;zhi'ke'y
accessibilityFlags:很关键,你的应用程序需要获取哪些信息:1.flagDefault默认; 2.flagIncludeNotImportantViews显示所有视图节点(主要是效率,才会有这个属性);
description:该服务的简要描述(会在开启辅助功能页面看到这段文字)
notificationTimeout:响应时间间隔100就好了
packageNames:需要辅助的app包名,不写表示所有
anRetrieveWindowContent:是否希望能够检索活动窗口内容。此设置无法在运行时更改。
appcanPerformGestures:允许app发送手势(api24及以上才可以使用手势),肯定true了
2.4,启动服务时先判断有没有启动,没有跳转到开启服务页面(不能通过startService开启服务)
if (!KeyService.isAccessibilitySettingsOn(MainActivity.this)) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
2.5,从KeyService服务代码可以看出创建了一个Window,如果能正常运行,还需要在AndroidManifest申请权限;
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
此权限在6.0上以上版本还要动态授权;不然会报错;动态授权如下:
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
// Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// startActivityForResult(intent, 1);
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 0);
} else {
//TODO do something you need
}
}
以上就可以模拟物理按键了;如果出现按键不起作用,请重启系统;
二,Instrumentation实现
1,可以模拟的按键就比较多了,按键值定义在系统API KeyEvent类中,部分源代码如下:
public class KeyEvent extends InputEvent implements Parcelable {
/** Key code constant: Unknown key code. */
public static final int KEYCODE_UNKNOWN = 0;
/** Key code constant: Soft Left key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom left
* of the display. */
public static final int KEYCODE_SOFT_LEFT = 1;
/** Key code constant: Soft Right key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom right
* of the display. */
public static final int KEYCODE_SOFT_RIGHT = 2;
/** Key code constant: Home key.
* This key is handled by the framework and is never delivered to applications. */
public static final int KEYCODE_HOME = 3;
/** Key code constant: Back key. */
public static final int KEYCODE_BACK = 4;
/** Key code constant: Call key. */
public static final int KEYCODE_CALL = 5;
/** Key code constant: End Call key. */
public static final int KEYCODE_ENDCALL = 6;
/** Key code constant: '0' key. */
public static final int KEYCODE_0 = 7;
/** Key code constant: '1' key. */
public static final int KEYCODE_1 = 8;
/** Key code constant: '2' key. */
public static final int KEYCODE_2 = 9;
/** Key code constant: '3' key. */
public static final int KEYCODE_3 = 10;
/** Key code constant: '4' key. */
public static final int KEYCODE_4 = 11;
/** Key code constant: '5' key. */
public static final int KEYCODE_5 = 12;
/** Key code constant: '6' key. */
public static final int KEYCODE_6 = 13;
/** Key code constant: '7' key. */
public static final int KEYCODE_7 = 14;
/** Key code constant: '8' key. */
public static final int KEYCODE_8 = 15;
/** Key code constant: '9' key. */
public static final int KEYCODE_9 = 16;
/** Key code constant: '*' key. */
public static final int KEYCODE_STAR = 17;
/** Key code constant: '#' key. */
public static final int KEYCODE_POUND = 18;
/** Key code constant: Directional Pad Up key.
* May also be synthesized from trackball motions. */
public static final int KEYCODE_DPAD_UP = 19;
/** Key code constant: Directional Pad Down key.
* May also be synthesized from trackball motions. */
public static final int KEYCODE_DPAD_DOWN = 20;
/** Key code constant: Directional Pad Left key.
* May also be synthesized from trackball motions. */
public static final int KEYCODE_DPAD_LEFT = 21;
/** Key code constant: Directional Pad Right key.
* May also be synthesized from trackball motions. */
public static final int KEYCODE_DPAD_RIGHT = 22;
/** Key code constant: Directional Pad Center key.
* May also be synthesized from trackball motions. */
public static final int KEYCODE_DPAD_CENTER = 23;
/** Key code constant: Volume Up key.
* Adjusts the speaker volume up. */
public static final int KEYCODE_VOLUME_UP = 24;
/** Key code constant: Volume Down key.
* Adjusts the speaker volume down. */
public static final int KEYCODE_VOLUME_DOWN = 25;
/** Key code constant: Power key. */
public static final int KEYCODE_POWER = 26;
/** Key code constant: Camera key.
* Used to launch a camera application or take pictures. */
public static final int KEYCODE_CAMERA = 27;
/** Key code constant: Clear key. */
public static final int KEYCODE_CLEAR = 28;
/** Key code constant: 'A' key. */
public static final int KEYCODE_A = 29;
/** Key code constant: 'B' key. */
public static final int KEYCODE_B = 30;
/** Key code constant: 'C' key. */
public static final int KEYCODE_C = 31;
/** Key code constant: 'D' key. */
public static final int KEYCODE_D = 32;
/** Key code constant: 'E' key. */
public static final int KEYCODE_E = 33;
/** Key code constant: 'F' key. */
public static final int KEYCODE_F = 34;
/** Key code constant: 'G' key. */
public static final int KEYCODE_G = 35;
/** Key code constant: 'H' key. */
public static final int KEYCODE_H = 36;
/** Key code constant: 'I' key. */
public static final int KEYCODE_I = 37;
/** Key code constant: 'J' key. */
public static final int KEYCODE_J = 38;
/** Key code constant: 'K' key. */
public static final int KEYCODE_K = 39;
/** Key code constant: 'L' key. */
public static final int KEYCODE_L = 40;
/** Key code constant: 'M' key. */
public static final int KEYCODE_M = 41;
/** Key code constant: 'N' key. */
public static final int KEYCODE_N = 42;
/** Key code constant: 'O' key. */
public static final int KEYCODE_O = 43;
/** Key code constant: 'P' key. */
public static final int KEYCODE_P = 44;
/** Key code constant: 'Q' key. */
public static final int KEYCODE_Q = 45;
/** Key code constant: 'R' key. */
public static final int KEYCODE_R = 46;
/** Key code constant: 'S' key. */
public static final int KEYCODE_S = 47;
/** Key code constant: 'T' key. */
public static final int KEYCODE_T = 48;
/** Key code constant: 'U' key. */
public static final int KEYCODE_U = 49;
/** Key code constant: 'V' key. */
public static final int KEYCODE_V = 50;
/** Key code constant: 'W' key. */
public static final int KEYCODE_W = 51;
/** Key code constant: 'X' key. */
public static final int KEYCODE_X = 52;
/** Key code constant: 'Y' key. */
public static final int KEYCODE_Y = 53;
/** Key code constant: 'Z' key. */
public static final int KEYCODE_Z = 54;
}
2,基础代码很简单;
public static void onKeyEvent(final int keyCode) {
new Thread() {
public void run() {
try {
Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync(keyCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
3,查看源码发现不能在主线程调用
Instrumentation中的sendKeyDownUpSync
public void sendKeyDownUpSync(int key) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
}
sendKeyDownUpSync又调用了sendKeySync方法:
public void sendKeySync(KeyEvent event) {
validateNotAppThread();
long downTime = event.getDownTime();
long eventTime = event.getEventTime();
int source = event.getSource();
if (source == InputDevice.SOURCE_UNKNOWN) {
source = InputDevice.SOURCE_KEYBOARD;
}
if (eventTime == 0) {
eventTime = SystemClock.uptimeMillis();
}
if (downTime == 0) {
downTime = eventTime;
}
KeyEvent newEvent = new KeyEvent(event);
newEvent.setTime(downTime, eventTime);
newEvent.setSource(source);
newEvent.setFlags(event.getFlags() | KeyEvent.FLAG_FROM_SYSTEM);
InputManager.getInstance().injectInputEvent(newEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
判断条件在validateNotAppThread
private final void validateNotAppThread() {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException(
"This method can not be called from the main application thread");
}
}
如果运行如上代码可能会报错,提示需要申请权限:<uses-permission android:name="android.permission.INJECT_EVENTS"/>和申请系统应用android:sharedUserId="android.uid.system" 但是此权限为系统应用才能申请,所以要系统签名的app才行;
三,通过input命令
基础代码:keyCode值也是KeyEvent中定义的值;
条件:系统需要root了,而且APP也能获取到root权限;
/**
* input命令实现
* 执行shell 命令, 命令中不必再带 adb shell
* @param keyCode KeyEvent列出的keyCode值
* @return Sting 命令执行在控制台输出的结果
*/
public static String execByRuntime(int keyCode) {
String cmd = "input keyevent" + keyCode;
Process process = null;
BufferedReader bufferedReader = null;
InputStreamReader inputStreamReader = null;
try {
process = Runtime.getRuntime().exec(cmd);
inputStreamReader = new InputStreamReader(process.getInputStream());
bufferedReader = new BufferedReader(inputStreamReader);
int read;
char[] buffer = new char[4096];
StringBuilder output = new StringBuilder();
while ((read = bufferedReader.read(buffer)) > 0) {
output.append(buffer, 0, read);
}
return output.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (null != inputStreamReader) {
try {
inputStreamReader.close();
} catch (Throwable t) {
}
}
if (null != bufferedReader) {
try {
bufferedReader.close();
} catch (Throwable t) {
}
}
if (null != process) {
try {
process.destroy();
} catch (Throwable t) {
}
}
}
}