티스토리 뷰

728x90

이번에는 안드로이드에서 특정앱의 실행을 방지하는 기능을 구현하는 방법에 대해 알아보겠습니다.

간혹 아동 공부집중력 향상을 위해 게임앱이나 인터넷 앱 실행을 블록시킨다던지 아니면 비정상적인 앱 사용을 막기위해 불법 프로그램 앱의 실행을 막는다던지 어느 특정앱의 실행을 막고싶을때가 있습니다. 예를들면 게임 내에서 쉬운 성장을 위한 매크로앱 같은것이 되겠습니다. 안드로이드에서는 이러한 어느 특정앱을 지정하여서 차단시킬 수 있습니다. 그럼 어떻게 구현하는지 본격적으로 알아봅시다.

 

차단대상 앱이 언제 실행될지는 명시적으로 알 수가 없기 때문에 우리는 항상 차단앱이 실행되는지 감시하고 있어야합니다. 이를 위해서 안드로이드의 컴포넌트중 하나인 서비스를 이용해야합니다. 우리가 개발할 앱 내에서 차단앱의 실행을 감지하는 기능을 탑재만 서비스를 구현하여 사용자로부터 권한을 허용받고 서비스를 실행시켜놓으면 사용자가 우리의 앱을 실행한 상태가 아니더라도 핸드폰이 켜져있을 때 항상 차단앱이 실행되는지 안되는지 감시할 수 있습니다.

 

이때 필요한 권한이 접근성(Accessibility) 권한입니다. 만약 접근성 권한을 사용자로부터 허용받지 못한다면 차단 기능을 서비스할 수 없습니다. 사용자의 허용없이 강제적으로 수행한다면 이는 구글의 앱 규제에 걸려 배포조차 할 수 없을것입니다. 

 

그럼 우선 사용자에게 접근성 권한을 얻어보겠습니다. 

사용자로부터 접근성 권한을 언제 얻을지는 본인의 개발 설계에 따라 다릅니다. 접근성 권한을 얻을 시점에 있는 액티비티 클래스에 다음 코드를 추가해줍시다.

    public boolean checkAccessibilityPermissions(){
        AccessibilityManager accessibilityManager = 
        		(AccessibilityManager)getSystemService(Context.ACCESSIBILITY_SERVICE);

        List<AccessibilityServiceInfo> list = 
        	accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);

        Log.d("service_test", "size : " + list.size());
        for(int i = 0; i < list.size(); i++){
            AccessibilityServiceInfo info = list.get(i);
            if(info.getResolveInfo().serviceInfo.packageName.equals(getApplication().getPackageName())){
                return true;
            }
        }
        return false;
    }

    public void setAccessibilityPermissions(){
        AlertDialog.Builder permissionDialog = new AlertDialog.Builder(this);
        permissionDialog.setTitle("접근성 권한 설정");
        permissionDialog.setMessage("앱을 사용하기 위해 접근성 권한이 필요합니다.");
        permissionDialog.setPositiveButton("허용", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
                return;
            }
        }).create().show();
    }

접근성 권한이 이미 허용되어있는지 파악하기 위한 메서드와 허용되어있지 않을 때 사용자에게 권한 허용을 묻는 다이얼로그 창을 생성하는 메서드를 구현합니다. 두 메서드의 이름은 여러분이 자유롭게 코딩 습관대로 수정하여도 무방합니다.

첫번째 메서드에서는 접근성 권한 매니저를 통해 존재하는 모든 접근성 권한 정보를 가져오고 존재하는 접근성 권한중 우리의 앱이 있는지 확인합니다. 

두번째 메서드에서는 다이얼로그 형식의 액티비티를 생성하여 사용자에게 허용 혹은 거부를 설정하게 합니다. 제가 진행한 프로젝트에서는 접근성 권한을 필수요소로 설계하였기 때문에 거부할시 앱을 사용할 수 없게 하였습니다. 여기에 대한 부분은 여러분의 앱 설계의도에 맞춰 개발하시면 됩니다.

 

이제 서비스 컴포넌트를 구현해야합니다.

프로젝트 폴더에 AccessibilityService 클래스를 상속받는 클래스 파일을 생성합니다. 그리고 ctrl+o단축키를 눌러 onAccessibilityEvent 메서드를 오버라이드 하고 다음 코드를 입력합니다.

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        boolean denyApp = false;
        if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if(packagename.equals(event.getPackageName())) {
                Toast.makeText(this.getApplicationContext(), event.getPackageName() + "앱이 거부되었습니다", Toast.LENGTH_LONG);
                gotoHome();
            }

//            Log.e(TAG, "Catch Event Package Name : " + event.getPackageName());
//            Log.e(TAG, "Catch Event TEXT : " + event.getText());
//            Log.e(TAG, "Catch Event ContentDescription : " + event.getContentDescription());
//            Log.e(TAG, "Catch Event getSource : " + event.getSource());
//            Log.e(TAG, "=========================================================================");
        }
    }

코드를 분석해보면 가장 첫 if문에서 디바이스의 화면의 상태가 변화할때마다 이벤트를 감지합니다. 그렇다면 그 어떤앱이든 실행시킬때마다 화면의 상태는 변하기 때문에 해당 이벤트가 발생하겠죠. 그리고 이 이벤트가 발생하면 화면이 변하면서 전면에 드러난 앱의 패키지명이 무엇인지 확인합니다. 위에서 packagename.equals 부분이 여러분이 차단을 원하는 대상의 앱 패키지명이며 이 패지키명이 이벤트를 통해 가져온 event.getPackageName()과 동일할시 앱을 종료시킵니다. 이제 앱의 실행을 감지하는것에는 성공하였으니 앱을 종료시키는법을 알아봅시다.

 

위 코드에서 gotoHome()부분이 앱을 종료시키는 메서드를 호출하는 부분입니다. 메서드 이름을 여러분 코드성격에 맞게 변경한뒤 다음 코드를 입력합니다.

    private void gotoHome(){
        Intent intent = new Intent();
        intent.setAction("android.intent.action.MAIN");
        intent.addCategory("android.intent.category.HOME");
        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                | Intent.FLAG_ACTIVITY_FORWARD_RESULT
                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        startActivity(intent);
    }

코드에 어려운 부분은 없습니다. 단순히 인텐트에 적절한 플래그를 넣어 홈화면으로 되돌아가도록 하는 코드입니다. 이렇게 한다면 차단대상 앱이 켜짐과 동시에 홈버튼이 눌리는 것처럼 동작하여 차단앱을 실행시킬수 없게됩니다.

 

이제 접근성 서비스를 메니페스트에 등록하겠습니다.

res폴더에 xm폴더를 생성하고 여기에 accessibility_service_config.xml파일을 생성합니다. 그리도 다음과 같이 작성합니다.

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_description"
    android:notificationTimeout="100" />

그후에 이 파일을 메니페스트 파일에 적용시키기 위해 application태그안에 다음 코드를 추가합니다.

        <service
            android:name=".YourClassName"
            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>

name값은 위에서 생성한 클래스의 이름이어야합니다. 만약 다른 이름을 입력한다면 오류가 발생합니다.

이제 모든 준비를 마쳤습니다. 정상적으로 순서에 따랐다면 여러분이 원하는 앱이 실행되었을 때 바로 홈화면으로 돌아가게 될 것입니다.

잘 안되시는 분은 질문 남겨주시고 한분한분 정성껏 답변드려보도록 하겠습니다. 감사합니다.

 

+02.17 수정사항 : accessibility_service_config.xml에서 설정한 서비스의 서비스타입과 checkAccessibilityPermissions() 메서드에서 확인하는 서비스타입이 불일치하는 오류가 있어 기존 

List<AccessibilityServiceInfo> list = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.DEFAULT);

 

코드를 아래와같이 수정하였습니다.

List<AccessibilityServiceInfo> list = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);

댓글