实战Android:用AccessibilityService捕获volume按键
要在后台捕获并处理按键,AccessibilityService是个好办法。当然其局限性也很明显,其一,AccessibilityService和其他Service最大的一个区别就是,必须在Setting->Accessibity Setting中获得用户的许可。而且,一旦启动,他的管理就在于操作系统,你无法在中途将其退出(不用的时候占着资源,挺烦的)。其二,对于用户而言,在后台弄这么一个有某方面监控大权的app,也不是一个很好的体验。
言归正传,下面我们来看源码说明。
1。Notification
我这里为了让用户明确看到程序在后台,特地挂了个Notification, (accessIntent.setAction("com.android.vending");这句是为了避免explicit报错)。
accessIntent = new Intent(MainActivity.this, KeyAccessibilityService.class);
accessIntent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
accessIntent.setAction("com.android.vending");
如果你不想要那个Notification显示在手机前台,可以这么写
accessIntent = new Intent();
accessIntent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
accessIntent.setAction("com.android.vending");
2。避免不需要响应时拦截按键信息
由于是常驻内存,所以为了避免不需要响应时拦截按键信息,定义
public static boolean isAccessAlive = false;
当需要拦截时,把isAccessAlive设置成true,当不需要拦截时,设置成false就不影响系统的音量调节了(我这里以音量键为例写的)。
3。使用时需要设置AccessibityService
我用的华为手机,安装完后,其设置是在
设置-》高级设置-》辅助功能-》keyActionService
这个keyActionService就是我们的监听程序。其他手机可能不一样,Android模拟机中是setting->accessibility。
这个显示的界面,其xml文件就是res->xml->accessibility_service_config.xml。
运行时debug显示捕捉到的按键信息如下,
源码
如下,
布局activity_main.java
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
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=".MainActivity">
<Button
android:id="@+id/btn_white"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/service_white" />
<Button
android:id="@+id/btn_stop_white"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/stop_white" />
<Button
android:id="@+id/btn_exit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/exit" />
</LinearLayout>
MainActivity.java
package com.spacesoftwares.spacecapture;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.spacesoftwares.spacecapture.service.KeyAccessibilityService;
public class MainActivity extends AppCompatActivity {
private Button mBtnWhite, mBtnStopWhite, mBtnExit; // mBtnBlack, mBtnStopBack,
private final static String TAG = MainActivity.class.getSimpleName();
private Intent accessIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnWhite = findViewById(R.id.btn_white);
mBtnStopWhite = findViewById(R.id.btn_stop_white);
mBtnExit = findViewById(R.id.btn_exit);
setListener();
}
@Override
protected void onDestroy() {
KeyAccessibilityService.isAccessAlive = false;
super.onDestroy();
}
class ExitReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
MainActivity.this.finish();
}
}
private void setListener(){
OnClick onClick = new OnClick();
mBtnWhite.setOnClickListener(onClick);
mBtnStopWhite.setOnClickListener(onClick);
mBtnExit.setOnClickListener(onClick);
}
private class OnClick implements View.OnClickListener{
@Override
public void onClick(View view) {
int viewId = view.getId();
switch(viewId){
case R.id.btn_white:
Log.i(TAG, "MAIN: btn_white");
if(null == accessIntent) {
// ---- method No. 1 ----
//accessIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
//accessIntent.setPackage("com.android.vending");
// ---- method No. 2 ---- cannot get the key
accessIntent = new Intent(MainActivity.this, KeyAccessibilityService.class);
accessIntent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
accessIntent.setAction("com.android.vending");
}
startService(accessIntent);
KeyAccessibilityService.isAccessAlive = true;
break;
case R.id.btn_stop_white:
Log.i(TAG, "MAIN: btn_stop_white");
if(null != accessIntent)
stopService(accessIntent);
KeyAccessibilityService.isAccessAlive = false;
break;
case R.id.btn_exit:
Log.i(TAG, "MAIN: btn_exit");
if(null != accessIntent)
stopService(accessIntent);
finish();
KeyAccessibilityService.isAccessAlive = false;
System.exit(0);
break;
}
}
}
/*@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch(keyCode){
case KeyEvent.KEYCODE_VOLUME_UP:
isVolumeUpKeyPressed = true;
System.out.println("keycode_volume_up");
Log.i(TAG,"keycode_volume_up");
return true;
case KeyEvent.KEYCODE_POWER:
System.out.println("keycode_power");
Log.i(TAG,"keycode_power");
return true;
default:
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return super.onKeyLongPress(keyCode, event);
}*/
}
KeyAccessbilityService.java
package com.spacesoftwares.spacecapture.service;
import android.accessibilityservice.AccessibilityService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import com.spacesoftwares.spacecapture.MainActivity;
import com.spacesoftwares.spacecapture.R;
import java.util.Calendar;
public class KeyAccessibilityService extends AccessibilityService {
public static boolean isAccessAlive = false;
private final static int FOREGROUND_ID = 1000;
private Notification mNotification;
private static final String TAG_KEY = "KEY_ACCESS";
@Override
protected boolean onKeyEvent(KeyEvent event) {
System.out.println("onKeyEvent");
Log.i(TAG_KEY, "onKeyEvent");
if(isAccessAlive) {
int key = event.getKeyCode();
switch (key) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
System.out.println("KEYCODE_VOLUME_DOWN");
Log.i(TAG_KEY, "KEYCODE_VOLUME_DOWN");
return true;
case KeyEvent.KEYCODE_VOLUME_UP:
System.out.println("KEYCODE_VOLUME_UP");
Log.i(TAG_KEY, "KEYCODE_VOLUME_UP");
return true;
default:
break;
}
}
return super.onKeyEvent(event);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(null != mNotification){
Log.i(TAG_KEY, "KeyService->onStartCommand->Notification exists");
return super.onStartCommand(intent, flags, startId);
}
Log.i(TAG_KEY, "KeyService->onStartCommand->Create Notification");
//NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
//NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String NOTIFICATION_CHANNEL_ID = "my_channel_id_01";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "My Notifications", NotificationManager.IMPORTANCE_HIGH);
// Configure the notification channel.
notificationChannel.setDescription("Channel description");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setVibrationPattern(new long[]{0, 1000, 500, 1000});
notificationChannel.enableVibration(true);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(notificationChannel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("Foreground");
builder.setContentText("Text shown on notification bar");
builder.setContentInfo("Content Info");
builder.setWhen(System.currentTimeMillis());
Intent activityIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
mNotification = builder.build();
startForeground(FOREGROUND_ID, mNotification);
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onServiceConnected() {
System.out.println("KEYCODE_onServiceConnected");
Log.i(TAG_KEY, "KEYCODE_onServiceConnected");
super.onServiceConnected();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
System.out.println("KEYCODE_onAccessibilityEvent");
Log.i(TAG_KEY, "KEYCODE_onAccessibilityEvent");
}
@Override
public void onInterrupt() {
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.spacesoftwares.spacecapture">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.KeyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="@string/keyActionService"
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/accessibility_service_config" />
</service>
</application>
</manifest>
res->xml->accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagRequestFilterKeyEvents"
android:canRetrieveWindowContent="true"
android:canRequestFilterKeyEvents="true"
android:description="@string/accessibility_description"
android:notificationTimeout="1000"/>
完整的项目资源在这里下载,