WORK/Android

[Android] 화면 OFF 상태에서 Key Event 받기 | AcessibilityService 상속 서비스 | KeyEvent 서비스 | 접근성 서비스

호이호이호잇 2022. 4. 1. 20:10
728x90
반응형

안드로이드 단말에는 다양한 키들이 존재한다.

 

볼륨 Up/Down, 전원 등등의 키가 있고 이 키들이 press/release 되는 이벤트를 앱에서 받아 처리 할 수 있다.

아래 링크에서 안드로이드가 제공해주는 기본 키들에 대한 정보를 알 수 있다.

https://developer.android.com/reference/android/view/KeyEvent

 

KeyEvent  |  Android Developers

 

developer.android.com

 

안드로이드 앱에서는 기본적으로 onKeyUp / onKeyDown 을 받아서 처리가 가능하다.

public class KeyEventActivity extends AppCompatActivity {

    private static final String TAG = "KeyEventActivity";
    private static final boolean Debug = true;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_keyevent);

    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event){
        if(Debug) Log.d(TAG, "onKeyUp - keyCode : "+keyCode);

        return false;
    }
}

 

다만 화면이 꺼지면, 앱은 Pause 상태에 빠지기 때문에 onKeyUp / onKeyDown 이벤트를 받는 것이 어렵다.

 

(참고 : 안드로이드 생명 주기 https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko#onpause )

 

활동 수명 주기에 관한 이해  |  Android 개발자  |  Android Developers

활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페

developer.android.com

 

이럴 경우 어떤 방법으로 처리를 해야 화면 ON/OFF 상태에 상관 없이 Key 이벤트를 처리 할 수 있을까?

 

바로바로

Accessibility Service 를 활용하는 방법이다.

 

안드로이드 개발자 홈페이지에 아주 잘 나와있어서 내가 만든 예제와 참고해서 보면 좋을 것 같다.

https://developer.android.com/guide/topics/ui/accessibility/service

 

나만의 접근성 서비스 만들기  |  Android 개발자  |  Android Developers

나만의 접근성 서비스 만들기 접근성 서비스는 장애를 가진 사용자 또는 일시적으로 기기와 완벽하게 상호작용할 수 없는 사용자를 지원하기 위해 사용자 인터페이스를 향상하는 애플리케이션

developer.android.com

 

내가 만든 예제는 다음과 같이 이루어져 있다.

 

Step1. 사용자로부터 Accessibility 권한 받기

나는 이 기능을 맨 처음 실행되는 Activity에 넣어 구현하였다.

어디에 넣어도 상관 없을 것 같긴한데,

KeyEvent 가 필요한 시점에는 무조건 권한이 필요하기 때문에 맨 처음 받는 것이 좋을 것 같다.

(보통 앱 설치 후 첫 실행에 모든 퍼미션을 요청하니까..)

 

Step1에 넣은 것과 달리.. 나는 이 작업을 안해줘서 계속 헤맸다..

달리 에러도 안나고 잘 실행되는 것 처럼 보여서 찾기가 정말 힘들었다.!

그래서 Step1..!!!

 

-1. 현재 앱 권한 확인

Accessibility 권한이 있는 리스트 중 현재 실행되고 있는 앱이 있는지 확인하는 방식

/**
 * Check for accessibility permission of current app.
 * 
 * @return  - true : permission exist.
 *          - false : permission not exist.
 *
 */
public boolean checkAccessibilityPermissions() {
    AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(this.ACCESSIBILITY_SERVICE);

    // getEnabledAccessibilityServiceList use for getting a list of apps that have accessibility permission.
    List<AccessibilityServiceInfo> list = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.DEFAULT);

    for (int i = 0; i < list.size(); i++) {
        AccessibilityServiceInfo info = list.get(i);

        // Check current app exist in the list.
        // Compare with package name of app.
        if (info.getResolveInfo().serviceInfo.packageName.equals(getApplication().getPackageName())) {
            return true;
        }
    }
    return false;
}

 

-2. 권한 설정 Activity 실행

권한이 없다면 권한 설정을 받을 수 있는 Acitivity 를 실행.

/**
 * Execute to permission settings.
 */
public void setAccessibilityPermissions() {
    AlertDialog.Builder gsDialog = new AlertDialog.Builder(this);
    gsDialog.setTitle("Setting Accessibility Permission");
    gsDialog.setMessage("Need Accessibility Permission");
    gsDialog.setPositiveButton("Check", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            // Start setting permission activity
            startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
            return;
        }
    }).create().show();
}

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final boolean Debug = true;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // After check accessibility permission, execute setting permission activity.
        if(!checkAccessibilityPermissions()) {
            setAccessibilityPermissions();
        }
    }

    /**
     * Check for accessibility permission of current app.
     * 
     * @return  - true : permission exist.
     *          - false : permission not exist.
     *
     */
    public boolean checkAccessibilityPermissions() {
        AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(this.ACCESSIBILITY_SERVICE);

        // getEnabledAccessibilityServiceList use for getting a list of apps that have accessibility permission.
        List<AccessibilityServiceInfo> list = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.DEFAULT);

        for (int i = 0; i < list.size(); i++) {
            AccessibilityServiceInfo info = list.get(i);

            // Check current app exist in the list.
            // Compare with package name of app.
            if (info.getResolveInfo().serviceInfo.packageName.equals(getApplication().getPackageName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Execute to permission settings.
     */
    public void setAccessibilityPermissions() {
        AlertDialog.Builder gsDialog = new AlertDialog.Builder(this);
        gsDialog.setTitle("Setting Accessibility Permission");
        gsDialog.setMessage("Need Accessibility Permission");
        gsDialog.setPositiveButton("Check", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // Start setting permission activity
                startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
                return;
            }
        }).create().show();
    }
}

 

 

Step2.  Accessibility 상속 받은 서비스 생성

대망의 서비스 등장!

서비스 코드는 사실 별게 없는 것 같고..

Manifest가 중요하다!

 

- KeyEventServiceAccessibility.java

public class KeyEventServiceAccessibility extends AccessibilityService {

    private static final String TAG = "KeyEventServiceAccessibility";
    private static final boolean Debug = true;

    public KeyEventServiceAccessibility() {
        Log.d(TAG, "KeyEventServiceAccessibility");
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        Log.d(TAG, "onAccessibiltyEvent" + accessibilityEvent.toString());
    }

    @Override
    public void onInterrupt() {

    }

    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        Log.d(TAG, "onKeyEvent - keyCode = " + event.getKeyCode());
        return handleKeyEvent(event);
    }

    private boolean handleKeyEvent(KeyEvent event) {
        Log.d(TAG, "handleKeyEvent - keyCode = " + event.getKeyCode());
        int action = event.getAction();
        int keyCode = event.getKeyCode();
        if (action == KeyEvent.ACTION_DOWN) {
            // Key press
        } else if(action == KeyEvent.ACTION_UP) {
            // Key Release
        }
        
        return false;
    }
}

 

- AndroidManifest.xml

여기서도 내가 엄청 헤맸었는데,..

android:accessibilityFlags="flagRequestFilterKeyEvents" 이 옵션을 넣지 않아서 KeyEvent를 못받고, onAccessibilityEvent 이벤트만 계속 받았다는 슬픈이야기.. 저 옵션을 추가하니까 마법처럼 keyEvent를 받을 수 있었다.!

<service
    android:name=".KeyEventServiceAccessibility"
    android:accessibilityFlags="flagRequestFilterKeyEvents"
    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/accessibilityservice" />
</service>

 

이렇게 2단계를 이용하면 Accessibility 서비스를 이용할 수 있다.

 

 

하지만 모든 Key 에 대한 이벤트가 올라오는 것은 아니며,

화면이 OFF 되어 있을때는 더더더더 확인 가능한 key가 적다.!

 

이에대한 key 동작을 알아보는 것은 다음 게시물로 써야징~~

 

 

 

728x90
반응형