본문 바로가기

프로젝트/블루투스 무드등

[비전공자도 만들 수 있는 블루투스 무드등] 9. 안드로이드 앱 part 2-3 with 위험 권한

안녕하세요? 닉네임간편입니다. 이번 시간에는 페어링되지 않은 기기를 검색하는 데 필요한 위험 권한에 대해 알아보겠습니다.


전체 소스는 여기에 있습니다.

https://github.com/creativeduck/MyLED

앱을 미리 사용해보고 계신 분들은, 이 링크를 타고 설치해주시면 됩니다.

https://play.google.com/store/apps/details?id=com.mybest.myled


1. 일반 권한과 위험 권한

앞서 선언했던 권한은 일반 권한과 위험 권한으로 나뉩니다. BLUETOOTH와 BLUETOOTH_ADMIN은 일반 권한입니다.

API 레벨 23(마시멜로)부터는 '위험'한 권한에 대해선 다른 권한과 다르게 권한을 부여받도록 설정했습니다.

일반 권한은 설치 시간 권한이라고도 불리며, 말 그대로 앱을 설치했을 때 권한을 부여받습니다. 따라서 해당 권한을 부여하지 않으면 앱이 설치되지 않습니다.
일반 권한은 메니페스트 파일에 선언했다면 이후 추가적인 조치를 할 필요가 없습니다.

반면 위험 권한은 런타임 권한이라고도 불리며, 앱을 실행했을 때 권한을 부여받을 수 있습니다. 사진 관련 앱을 실행했을 때 갤러리 권한을 요청하는 화면이 뜨는 것을 보실 수 있을 겁니다. 이처럼 위험 권한은 메니페스트 파일에 선언을 했다고 하더라도 이후 추가적으로 앱 런타임, 즉 앱이 실행되었을 때 사용자로부터 권한을 부여받아야 합니다.

 

앞서 선언했던 권한들은 모두 일반 권한이기에 메니페스트 파일에 등록한 이후 추가적인 조치가 필요하지 않지만, 이번 시간에 선언할 권한은 위험 권한이므로 앱이 실행되었을 때 권한을 부여받을 수 있도록 조치를 취해야 합니다.

2. 위험 권한 선언

페어링되지 않은 기기를 검색할 때에는 주변 위치 정보가 필요하고, 이는 위험 권한에 해당하기 때문에 추가로 조치를 취해야 합니다. 따라서 먼저 위험 권한을 선언하는 방법에 대해 설명드리겠습니다.

 

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

우선 메니페스트 파일에 위치 정보에 대한 권한을 먼저 입력합니다.

위치 정보를 제공하는 권한은 이것 이외에 ACCESS_COARSE_LOCATION 권한도 있습니다. 

그러나 ACCESS_FINE_LOCATION 권한이 보다 더 정확한 위치를 제공하기 때문에 저는 이 권한만 선언하겠습니다.

 

String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};
checkPermissions(permissions);
static final int REQUEST_PERMISSIONS = 101;

 

1) String[] permissions

런타임에 권한을 부여받을 권한의 배열을 만듭니다. 현재 요청해야할 권한은 하나이지만 이후에 권한 요청을 받을 때 배열이 필요하기 때문에 요소 수가 한 개임에도 불구하고 배열로 선언했습니다.

 

2) checkPermissions(permissions)

권한이 이미 부여되었는지를 확인하고 권한 부여에 대한 조치를 취하는 메서드를 호출합니다.

 

3) static final int REQUEST_PERMISSIONS

위험 권한을 요청할 때의 코드값을 정의합니다.

 

private void checkPermissions(String[] permissions) {
        ArrayList<String> requestList = new ArrayList<>();

        for(int i=0; i<permissions.length; i++) {
            String curPermission = permissions[i];
            int permissionCheck = ContextCompat.checkSelfPermission(this, curPermission);
            if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
                if(ActivityCompat.shouldShowRequestPermissionRationale(this,curPermission)) {
                    Toast.makeText(getApplicationContext(), curPermission+" 권한 설명 필요", Toast.LENGTH_SHORT).show();
                } else {
                    requestList.add(curPermission);
                }
            }
        }
        if(requestList.size()>0) {
            String[] requests = requestList.toArray(new String[requestList.size()]);
            ActivityCompat.requestPermissions(this, requests, REQUEST_PERMISSIONS);
        }
    }

 

1) ArrayList<String> requestList = new ArrayList<>();

권한을 허가받지 않은 권한을 요소로 가지는 ArrayList를 만듭니다.

 

2) for문

앞서 요청할 권한에 대한 배열을 만들었습니다. 이 배열의 요소 수만큼 for문을 반복해서 조치를 취합니다.

 

a) String curPermission = permissions[i]

각 인덱스에 해당하는 권한을 가져와 curPermissions 문자열이 참조하도록 합니다.

 

b) int permissionCheck = ContextCompat.checkSelfPermission(this, curPermission)

이 권한이 이미 허가된 권한인지를 파악합니다. 이는 정수형 값을 반환하며 해당 값에 따라서 허가된 권한인지 아닌지를 구분할 수 있습니다. 이 값을 permissionCheck에 복사합니다.

 

c) permissionCheck == PackageManager.PERMISSION_GRANTED

만약 허가된 권한이라면 PackageManager.PERMISSION_GRANTED를 반환하고 이 값은 0입니다. 따라서 permissionCheck이 이 값과 같다면, 권한이 이미 부여된 것입니다. 따라서 토스트 메시지로 해당 권한이 있음을 알려줍니다.

허가된 권한에 대해선 다음과 같이 알려줍니다.

 

허가된 권한 토스트 메시지

 

d) ActivityCompat.shouldShowRequestPermissionRationale(this,curPermission)

만약 허가되지 않은 권한이라면, 추가적으로 이 권한에 대한 설명이 필요한지 여부를 확인합니다. 만일 설명이 필요하다면 shouldShowRequestPermissionRationale() 메서드는 true를 반환하고, 이 경우 토스트 메시지를 통해 권한 설명이 필요함을 알려줍니다.

 

e) requestList.add(curPermission)

설명이 따로 필요하지 않다면 이 권한을 앞선 requestList에 추가합니다.

 

3) 권한 요구

requestList.size()가 0보다 크다면, 즉 권한을 요청할 것이 있다면 동작을 수행합니다.

우선 앞서 만든 ArrayList를 문자열 배열로 만들어줍니다. 이후 requestPermissions() 메서드를 통해 권한을 요청할 배열과 요청 코드를 전달합니다.

권한을 요구하면 다음과 같은 대화상자가 화면에 나타납니다.

 

권한 허용 대화상자

 

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSIONS) {
            if (grantResults.length > 0) {
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(this, (i+1)+"번째 권한 승인", Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(this, (i+1)+"번째 권한 거부", Toast.LENGTH_SHORT).show();
                    }
                }
            }
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

 

메인 액티비티의 콜백 메서드를 재정의해서 권한 요청을 받았을 때 결과를 처리하도록 합니다.

 

1) 요청 코드

요청 코드가 우리가 앞서 전달한 REQUEST_PERMISSIONS라면 이후 동작을 수행하도록 합니다.

 

2) 권한 부여 배열

grantResults 배열은 권한이 부여받았는지 여부를 담고있는 배열입니다.

앞서 권한을 요청해야 하는 배열로 문자열 배열 requests를 만들었습니다. 이 배열의 순서대로 권한을 부여받았는지에 대한 여부가 grantResults에 저장됩니다.

예를 들어, 만약 requests의 0번째 인덱스에 해당하는 ACCESS_FINE_LOCATION이 허용되었다면, grantResults의 0번째 인덱스에 해당하는 요소에는 0이 담기게 됩니다.

권한을 부여받았다면 PackageManager.PERMISSION_GRANTED, 즉 0의 값을 담게 되기 때문입니다.

즉, 허용되어야 하는 권한의 순서대로 허용 여부를 저장하는 배열이 grantResults라고 할 수 있습니다.

 

따라서 for문을 통해 각 인덱스별로 권한 허용 여부에 따라 조치를 취합니다.

만일 i번째 인덱스에 해당하는 권한이 허용되었다면, 즉 PackageManager.PERMISSION_GRANTED의 값과 동일한 값을 갖고있다면, 토스트 메시지를 통해 권한이 승인되었음을 알려줍니다.

이때 (i+1)을 입력했습니다. 컴퓨터는 0번이 첫번째이기 때문에 사용자가 인지하기 쉽도록 1을 추가하였습니다.

권한이 허용되면 다음과 같은 메시지가 나옵니다.

 

권한 승인 메시지

 

만일 권한이 허가받지 않았다면, 권한이 거부되었음을 토스트 메시지로 알려줍니다.

 

권한 거부 시 발생하는 토스트 메시지

 

이후 super를 통해 부모 클래스의 해당 메서드가 해야할 일을 하도록 합니다.

 

 

3. 마무리

이번 시간에는 페어링되지 않은 기기를 탐색하기 위해 필요한 위험 권한 선언에 대해 알아보았습니다.

글이 다소 길어져서 페어링되지 않은 기기를 탐색하는 법은 다음 시간에 알아보겠습니다.

728x90
반응형