[Android 13] 알림 권한 허용 구현방법(POST_NOTIFICATIONS, areNotificationsEnabled(), 알림 설정창 호출 방법)
안드로이드 13 부터는 알림 메세지를 보냈을 때 사용자가 거부 또는 허용할 수 있도록 권한 허용을 요구해야한다. 포그라운드 서비스의 동작이 필수적으로 필요한 앱이라면 무조건 권한 부여를 받아야만 동작이 가능하게 된다.
안드로이드 13 알림권한 허용 방법
1. Manifest.xml 파일에 android.permission.POST_NOTIFICATIONS 권한을 추가해준다.
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
</application>
</manifest>
2. build.gradle(:app) 파일에서 compileSdkVersion과 targetSdkVersion을 33으로 상향시켜준다.
android {
compileSdkVersion 33 // ANDROID 13
// buildToolsVersion "29.0.2"
defaultConfig {
applicationId "smart.app......."
minSdkVersion 21
targetSdkVersion 33
versionCode 12
versionName "1.1.2"
vectorDrawables.useSupportLibrary = true //벡터이미지 사용 유무를 설정
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
3. MainActivity.java (앱의 시작뷰) 에서 안드로이드 13 티라미수 버전을 체크 메소드를 구현해주었다.
public static final int MY_PERMISSION_REQUEST_NOTIFICATION = 1020;
.....생략
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
checkAndroid13();
}
else {
this.backgroudServiceRestart();
}
4. 알림권한이 활성화 여부를 체크하는 함수를 호출하여 확인 후 권한이 있으면 백그라운드 서비스를 시작하고 그렇지 않으면 알림다이얼로그를 호출하여 사용자에게 권한 허용을 요구한다.
private void checkAndroid13(){
// 노티 권한 활성화 체크
if(NotificationManagerCompat.from(GeneralActivity.this).areNotificationsEnabled()) {
this.backgroudServiceRestart();
}else {
callNotiPermissionDialog();
}
}
private void callNotiPermissionDialog() {
try{
if (!GeneralActivity.this.isFinishing()) {
final Dialog personDialog = new Dialog(GeneralActivity.this);
// //setting custom layout to dialog
personDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
personDialog.setContentView(R.layout.easy_dialog_noti_guide);
if(personDialog.getWindow()!=null)
personDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0)); //Android: how to create a transparent dialog-themed activity
personDialog.setCancelable(false);
Button bunConfirm = (Button) personDialog.findViewById(R.id.bunConfirm);
bunConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.bunConfirm) {
if (!GeneralActivity.this.isFinishing() && personDialog != null && personDialog.isShowing()) {
personDialog.dismiss();
if (ActivityCompat.checkSelfPermission(GeneralActivity.this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { //권한 허용상태인지 체크
// requestPermissions 을 통해 권한 요청
ActivityCompat.requestPermissions(GeneralActivity.this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, MY_PERMISSION_REQUEST_NOTIFICATION);
}
}
}
}
});
if(!GeneralActivity.this.isFinishing() && personDialog!=null && !personDialog.isShowing()) {
personDialog.show();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
<easy_dialog_noti_guide.xml 레이아웃>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/profileChangeLayout"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_guide_shape_radius"
android:gravity="center">
<LinearLayout
android:id="@+id/topLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_title_auth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="@string/cont_68"
android:textColor="@color/colorxml_color_41"
android:textSize="20dp"
android:textStyle="bold"/>
</LinearLayout>
<LinearLayout
android:id="@+id/secondLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_shape_radius2"
android:gravity="left|center_horizontal"
android:orientation="vertical"
android:layout_below="@+id/topLayout">
<TextView
android:id="@+id/contentTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:textColor="@color/colorxml_color_41"
android:textSize="16sp"
android:text="@string/cont_67"/>
<LinearLayout
android:id="@+id/btnLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|center_horizontal"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<Button
android:id="@+id/bunConfirm"
android:layout_width="180dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:background="@drawable/btn_top_area_selector"
android:text="@string/btn_setting_text2"
android:textSize="14sp"
android:textColor="@color/colorxml_color_41"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
5. 퍼미션 콜백인 onRequestPermissionsResult 에서 승인이 된경우 백그라운드서비스를 시작해주고 미승인한 경우 다시 알림팝업을 노출하여 설정창으로 이동하도록 작성되었다.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if(requestCode != -1) { // && (requestCode&0xffff0000) != 0 //java.lang.IllegalArgumentException: Can only use lower 8 bits for requestCode 오류
switch (requestCode) {
case MY_PERMISSION_REQUEST_NOTIFICATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(GeneralActivity.this, "승인됨", Toast.LENGTH_LONG).show();
this.backgroudServiceRestart();
} else {
checkNotiPostPermissions();
Toast.makeText(GeneralActivity.this, "미승인", Toast.LENGTH_LONG).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
6. 알림팝업을 다시 호출하는 메소드를 구현 해서 알림 설정창으로 이동시킨다.
@TargetApi(Build.VERSION_CODES.TIRAMISU)
private void checkNotiPostPermissions() {
boolean isNotiPostRationale = shouldShowRequestPermissionRationale(android.Manifest.permission.POST_NOTIFICATIONS);
int hasPermission = ActivityCompat.checkSelfPermission(GeneralActivity.this, android.Manifest.permission.POST_NOTIFICATIONS);
if ( hasPermission == PackageManager.PERMISSION_DENIED && isNotiPostRationale) {
Toast.makeText(GeneralActivity.this, getResources().getString(R.string.cont_67), Toast.LENGTH_LONG).show();
// this.callGuideDialogUsingTimer(getResources().getString(R.string.info_info_text), getResources().getString(R.string.cont_67), 7000);
showDialogForNotiPermissionSetting();
} else if ( hasPermission == PackageManager.PERMISSION_DENIED && !isNotiPostRationale)
showDialogForNotiPermissionSetting();
else if ( hasPermission == PackageManager.PERMISSION_GRANTED ) {
// Toast.makeText(GeneralActivity.this, "승인됨", Toast.LENGTH_LONG).show();
this.backgroudServiceRestart();
}
}
7. 알림 설정창에서 Settings.ACTION_APP_NOTIFICATION_SETTINGS 인텐트를 호출해주고 결과를 리턴받는다.
Intent appDetail = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
appDetail.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
//appDetail.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(appDetail, 555);
public void showDialogForNotiPermissionSetting(){
try {
if (!GeneralActivity.this.isFinishing()) {
final Dialog guideDialog = new Dialog(GeneralActivity.this);
guideDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
guideDialog.setContentView(R.layout.easy_question_dialog);
guideDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0)); //Android: how to create a transparent dialog-themed activity
//guideDialog.setCanceledOnTouchOutside(false);
//guideDialog.setCancelable(false);
//String title = getResources().getString(R.string.ic_action_name2);
//String msg = getResources().getString(R.string.txt_8);
TextView txtContent = (TextView) guideDialog.findViewById(R.id.txtContent);
//TextView txtTitle = (TextView) guideDialog.findViewById(R.id.txtTitle);
//txtTitle.setText(title);
txtContent.setText(getResources().getString(R.string.cont_67));
Button buttonCancel = (Button) guideDialog.findViewById(R.id.buttonCancel);
buttonCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.buttonCancel) {
if (!GeneralActivity.this.isFinishing() && guideDialog != null && guideDialog.isShowing()) {
guideDialog.dismiss();
}
}
}
});
Button buttonConfirm = (Button) guideDialog.findViewById(R.id.buttonConfirm);
buttonConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.buttonConfirm) {
if (!GeneralActivity.this.isFinishing() && guideDialog != null && guideDialog.isShowing()) {
guideDialog.dismiss();
}
try {
Intent appDetail = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
appDetail.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
// appDetail.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(appDetail, 555);
}catch (Exception e){
//e.printStackTrace();
}
}
}
});
if(!GeneralActivity.this.isFinishing() && guideDialog != null && !guideDialog.isShowing()) {
guideDialog.show();
}
}
}catch (Exception e){
}
}
<easy_question_dialog.xml 레이아웃>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/profileChangeLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_shape_radius"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/baseline_eco_white_24"/>
<TextView
android:id="@+id/txtContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="10dp"
android:layout_marginLeft="2dp"
android:textColor="@color/colorxml_color_41"
android:textSize="16sp"
android:textStyle="bold"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
android:background="@color/main_rectangle_bg_color"/>
<LinearLayout
android:id="@+id/bottomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<Button
android:id="@+id/buttonCancel"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_weight="1"
android:background="@drawable/m_btn_selector"
android:text="@string/btn_cancel_text"
android:textColor="@color/colorxml_color_41"
android:textSize="16sp" />
<Button
android:id="@+id/buttonConfirm"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_weight="1"
android:background="@drawable/m_btn_selector"
android:text="@string/info_ok_text"
android:textColor="@color/colorxml_color_41"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
7. onActivityResult 콜백메소드에서 노티 권한 허용여부를 체크 후 다시 백그라운드 서비스를 시작한다.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case 555:
// 노티 권한 활성화 체크
if(NotificationManagerCompat.from(GeneralActivity.this).areNotificationsEnabled()) {
this.backgroudServiceRestart();
}
break;
case 1818:
//안드로이드 12 대응
if(android.os.Build.VERSION.SDK_INT >= 30) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null) {
if (pm.isIgnoringBatteryOptimizations(getPackageName())) {
this.backgroudServiceRestart();
} else {
//토스트메세지
CautionToast();
this.backgroudServiceRestart();
//다시호출
//callBatteryManagerDialog();
}
}
}
setStatusBatteryToggle();
break;
}
//}
}
기록해두지 않으면 또 다시 방황하는 날이 오기 마련인다. 항상 기록 해두자. 언제 어디서 어떻게 활용될지 아무도 모른다.
[REFERENCE]
- https://developer.android.com/develop/ui/views/notifications/notification-permission
- https://developer.android.com/reference/android/app/NotificationManager#areNotificationsEnabled()
- https://developer.android.com/about/versions/13/changes/notification-permission?hl=ko
- https://stackoverflow.com/questions/32366649/any-way-to-link-to-the-android-notification-settings-for-my-app
[연관자료]
- https://stackoverflow.com/questions/73067939/start-foreground-service-after-notification-permission-was-disabled-causes-crash
- https://stackoverflow.com/questions/72388741/android-manifest-post-notifications-missing-import
- https://stackoverflow.com/questions/73671885/is-there-a-difference-between-arenotificationsenabled-and-checkselfpermissi