Android

[안드로이드14 오류 처리] java.lang.SecurityException: Caller com.app.test needs to hold android.permission.SCHEDULE_EXACT_ALARM or android.permission.USE_EXACT_ALARM to set exact alarms.

안드로이드 14 운영체제가 배포되고 있는 모양이다. 삼성전자도 안드로이드 14 원UI 6 버전을 최종 베타 테스트를 진행하고 있고 이번주 중으로 정식 배포 버전이 나올 것으로 예상하고 있다. 생각보다 빨리 안드로이드14를 사용하는 사용자들이 늘어나고 있다.

기존에 문제없던 기능들에 대해 파이어베이스 크래시틱스 오류보가 지속적으로 증가하고 있다.

안드로이드14로 업그레이드 되면서 기존에 문제없던 기능들도 androidmenifest 파일에 권한을 명시해야 하는 것들이 생긴 모양이다.

문제는 안드로이드14를 타겟팅하고 있지않아도 오류가 발생됨으로 조치를 취해야한다.최근에 올라오는 오류들 중에 아래 오류 부터 처리를 진행했다.

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.app.test/com.app.test.SimpleMainActivity}: java.lang.SecurityException: Caller com.app.test needs to hold android.permission.SCHEDULE_EXACT_ALARM or android.permission.USE_EXACT_ALARM to set exact alarms.
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3782)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3922)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:205)
       at android.os.Looper.loop(Looper.java:294)
       at android.app.ActivityThread.main(ActivityThread.java:8177)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)


구글 안드로이드 가이드 문서 및 android14 프리뷰 블로그를 확인해보니 다음과 같은 내용이 추가되었다.

정확한 알람

정확한 알람을 호출하면 배터리 수명과 같은 기기의 리소스에 상당한 영향을 미칠 수 있습니다. 따라서 Android 14에서는 시계나 캘린더가 아닌 앱 중 Android 13 이상(SDK 33 이상)를 대상으로 새로 설치된 앱은 정확한 알람을 설정하기 전에 사용자에게 SCHEDULE_EXACT_ALARM 특별 권한을 부여하도록 요청해야 합니다. 앱은 이 권한을 전환하는 인텐트를 통해 사용자를 설정 페이지로 안내할 수 있지만, 사용 사례를 평가하고 가능하면 더 유연하게 예약된 대안을 선택하는 것이 좋습니다.

핵심 앱 워크플로의 일부로 정확한 알람에 의존하며 Android 13 이상(SDK 33 이상)을 대상으로 하는 시계 및 캘린더 앱은 대신 USE_EXACT_ALARM 일반 권한을 선언할 수 있습니다(설치 시 권한이 부여됨). 앱은 정책 언어를 기반으로 자격을 갖추지 않는 한 매니페스트에서 이 권한으로 Play 스토어에 앱의 버전을 게시할 수 없습니다.

영향을 받는 앱

기기에서 Android 14 이상을 실행하는 경우 이 변경사항은 다음에 새로 영향을 미칩니다. 다음과 같은 특성을 가진 설치된 앱:

정확한 알람 예약은 기본적으로 거부됨

정확한 알람은 사용자가 의도한 알림이나 정확한 시간에 실행해야 하는 작업을 위한 것입니다. Android 14부터 SCHEDULE_EXACT_ALARM 권한은 Android 13 이상을 타겟팅하는 새로 설치된 대부분의 앱에 더 이상 사전 부여되지 않습니다. 즉, 권한이 기본적으로 거부됩니다.

SCHEDULE_EXACT_ALARM: 앱에 대해 Android 12에 도입된 권한 정확한 알람 예약은 새로 설치된 대부분의 알람에 더 이상 사전 부여되지 않습니다. Android 13 이상을 타겟팅하는 앱(기본적으로 거부되도록 설정됨) 만약에 사용자는 Android 14를 실행하는 기기로 앱 데이터를 전송합니다. 백업 및 복원 작업을 수행하더라도 권한은 여전히 ​​거부됩니다. 만약 기존 앱에 이미 이 권한이 있으므로 기기가 실행될 때 사전 부여됩니다. Android 14로 업그레이드하세요.

정확한 알람을 시작하려면 SCHEDULE_EXACT_ALARM 권한이 필요합니다. 다음 API를 사용하지 않으면 SecurityException가 발생합니다.

참고: 다음과 같이 OnAlarmListener 개체를 사용하여 정확한 알람을 설정한 경우 setExact API의 경우 SCHEDULE_EXACT_ALARM 권한이 필요하지 않습니다.

SCHEDULE_EXACT_ALARM 권한에 대한 기존 모범 사례



영향을 받는 앱

기기에서 Android 14 이상을 실행하는 경우, 다음과 같은 특성을 가진 설치된 앱:

  • Android 13(API 수준 33) 이상을 대상으로 합니다.
  • 매니페스트에서 SCHEDULE_EXACT_ALARM 권한을 선언합니다.

오류가 발생한 코드는 다음과 같다.

    private void  alarmManagerSetting(){
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(SimpleMainActivity.this, SimpleAlarmReceiver.class);
//        PendingIntent sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, 0);
        //android 12
        PendingIntent sender;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        }else {
            sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, 0);
        }


        long currentTime = System.currentTimeMillis();
        //int interval = 5000;
        int interval = 10000*6*30;//30분
        if(alarmManager!=null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) //api level 23 (Marshmallow 6.0)
                alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
            else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                alarmManager.setExact(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
            else
                alarmManager.set(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
        }

    }

[오류 해결 가이드라인]

https://developer.android.com/about/versions/14/changes/schedule-exact-alarms?hl=ko

퍼미션 관련해서 안드로이드 12에서 이미 설정을 적용하였었다.

API Build.VERSION_CODES.TIRAMISU이 권한을 요청할 수 있으려면 앱이 타겟팅 이상이어야 합니다 . 장치에서는 또는 중 하나만 요청 해야 합니다. 앱이 이미 이전 SDK를 사용하고 있지만 SDK 33 이상에서 필요한 경우 다음과 같이 max-sdk 속성을 사용하여 선언해야 합니다 .USE_EXACT_ALARMSCHEDULE_EXACT_ALARMSCHEDULE_EXACT_ALARMUSE_EXACT_ALARMSCHEDULE_EXACT_ALARM

 Android 12(API 수준 31) 이상을 타겟팅하는 앱의 경우 setExactAndAllowWhileIdle(int, long, PendingIntent)
및 setAlarmClock(AlarmClockInfo, PendingIntent) 등 정확한 알람을 설정하는 API를 사용하려면 앱에
Manifest.permission.SCHEDULE_EXACT_ALARM 권한이 있어야 합니다. 

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
   android:maxSdkVersion="32" />


Android 12에서는 이 권한이 Android 시스템에 의해 자동으로 부여되지만 Android 13에서는 사용자가 이 권한을 부여했는지 확인해야 한다. 오류 처리를 위해 권한을 하나 더 추가해준다.

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
                 android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

그런 다음 권한이 부여되었는지 확인해야 하며, 부여되지 않은 경우 사용자를 알람 및 미리 알림 페이지로 리디렉션해야 합니다.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    val alarmManager = ContextCompat.getSystemService(context, AlarmManager::class.java)
    if (alarmManager?.canScheduleExactAlarms() == false) {
        Intent().also { intent ->
            intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
            context.startActivity(intent)
        }
    }
}

또한 Google에서는 Broadcast Receiver를 등록하여 이 권한에 대한 변경 사항을 확인하고 ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED 에서 변경 사항을 확인해야 한다.

권한 확인 없이 정확한 알림이 아니여도 된다면 아래와 같이 setWindow()함수를 사용하여 처리해도 된다.

private void  alarmManagerSetting(){
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(SimpleMainActivity.this, SimpleAlarmReceiver.class);
//        PendingIntent sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, 0);
        //android 12
        PendingIntent sender;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        }else {
            sender = PendingIntent.getBroadcast(SimpleMainActivity.this, REQUEST_CODE_ALARM_MANAGER, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }

        try {
            long currentTime = System.currentTimeMillis();
            //int interval = 5000;
            int interval = 10000 * 6 * 30;//30분
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                if (alarmManager.canScheduleExactAlarms()) {
                    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
                } else {
                    alarmManager.setWindow(AlarmManager.RTC_WAKEUP, currentTime, interval + interval, sender);
                }
            } else {
                if (alarmManager != null) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) //api level 23 (Marshmallow 6.0)
                        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
                    else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                        alarmManager.setExact(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
                    else
                        alarmManager.set(AlarmManager.RTC_WAKEUP, currentTime + interval, sender);
                }
            }

        }catch (Exception e) {
            e.printStackTrace();
        }

    }

[알람예약 공식 개발 가이드 문서 ]

https://developer.android.com/training/scheduling/alarms?hl=ko#java


[연관자료]

android adding DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION in release build

Leave a Reply

error: Content is protected !!