实战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"/>

完整的项目资源在这里下载,

https://download.csdn.net/download/tanmx219/10570716

posted @ 2018-07-29 13:52  SpaceVision  阅读(176)  评论(0编辑  收藏  举报