package com.example.shiftalarm import android.Manifest import android.app.Activity import android.app.AlarmManager import android.app.AlertDialog import android.app.NotificationManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.Settings import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import kotlinx.coroutines.* object AlarmPermissionUtil { private var monitorJob: Job? = null /** 권한 플로우가 진행 중인지 추적하는 플래그 (onResume 루프 방지) */ @Volatile var isPermissionFlowActive = false private set /** 현재 권한 모달 다이얼로그가 표시 중인지 추적 */ @Volatile private var isDialogShowing = false /** * 시스템 설정 화면에서 사용자가 권한을 허용했을 때 뒤로가기를 누르지 않아도 앱으로 즉시 자동 복귀시킵니다. */ fun monitorAndAutoReturn(activity: Activity, isGrantedCheck: () -> Boolean) { monitorJob?.cancel() monitorJob = CoroutineScope(Dispatchers.Main).launch { delay(1500) // 설정 화면 전환 시간 충분히 대기 (1.5초) var count = 0 while (count < 120 && isActive) { // 최대 60초간 500ms 주기로 체크 delay(500) count++ if (isGrantedCheck()) { try { val returnIntent = Intent(activity, SettingsActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP } activity.startActivity(returnIntent) } catch (e: Exception) { e.printStackTrace() } break } } } } fun cancelAutoReturn() { monitorJob?.cancel() monitorJob = null } /** * 전체 권한 상태를 확인하고 필요한 경우 통합 안내 다이얼로그를 표시합니다. * 권한 플로우가 이미 진행 중이거나 다이얼로그가 표시 중이면 중복 호출을 방지합니다. */ fun checkAndRequestAllPermissions(activity: ComponentActivity) { // 이미 권한 플로우 진행 중이면 중복 호출 방지 if (isPermissionFlowActive || isDialogShowing) return val missingPermissions = mutableListOf() // 1. 알림 권한 (Android 13+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { missingPermissions.add("알림 표시 (알람 울림 확인)") } } // 2. 정확한 알람 권한 (Android 12+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val am = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (!am.canScheduleExactAlarms()) { missingPermissions.add("정확한 알람 (정시에 울림 보장)") } } // 3. 배터리 최적화 제외 (Android 6+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = activity.getSystemService(Context.POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(activity.packageName)) { missingPermissions.add("배터리 최적화 제외 (절전 모드 무시)") } } // 4. 전체화면 알림 권한 (Android 14+) if (Build.VERSION.SDK_INT >= 34) { val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (!nm.canUseFullScreenIntent()) { missingPermissions.add("전체화면 알림 (잠금 화면에서 즉시 표시)") } } if (missingPermissions.isNotEmpty()) { showIntegratedPermissionDialog(activity, missingPermissions) } } private fun showIntegratedPermissionDialog(activity: ComponentActivity, missing: List) { if (activity.isFinishing || activity.isDestroyed) return if (isDialogShowing) return // 이미 모달이 떠있으면 중복 방지 isDialogShowing = true val message = StringBuilder("안정적인 알람 작동을 위해 아래 권한들이 필요합니다:\n\n") missing.forEach { message.append("- $it\n") } message.append("\n[확인]을 누르면 설정 화면으로 순차적으로 안내합니다.") try { AlertDialog.Builder(activity) .setTitle("권한 설정 안내") .setMessage(message.toString()) .setPositiveButton("확인") { _, _ -> isDialogShowing = false isPermissionFlowActive = true // 플로우 시작 - onResume 재호출 차단 startPermissionFlow(activity) } .setNegativeButton("나중에") { _, _ -> isDialogShowing = false // "나중에"를 누르면 이번 세션에서는 더 이상 묻지 않음 isPermissionFlowActive = true } .setCancelable(false) .setOnDismissListener { // 어떤 이유로든 다이얼로그가 닫히면 플래그 해제 isDialogShowing = false } .show() } catch (e: Exception) { isDialogShowing = false e.printStackTrace() } } /** * 권한 플로우 완료 후 플래그 리셋 (앱 재시작이나 설정에서 돌아올 때) */ fun resetPermissionFlowState() { isPermissionFlowActive = false isDialogShowing = false } private fun startPermissionFlow(activity: ComponentActivity) { // 순차적으로 가장 중요한 것부터 요청 // 1. 알림 권한 (시스템 팝업) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101) return } } // 2. 배터리 최적화 (시스템 팝업) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = activity.getSystemService(Context.POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(activity.packageName)) { requestBatteryOptimization(activity) return } } // 3. 정확한 알람 (설정 화면) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val am = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (!am.canScheduleExactAlarms()) { val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply { data = Uri.parse("package:${activity.packageName}") } activity.startActivity(intent) return } } // 4. 전체화면 알림 (설정 화면) if (Build.VERSION.SDK_INT >= 34) { val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (!nm.canUseFullScreenIntent()) { try { val intent = Intent("android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT").apply { data = Uri.parse("package:${activity.packageName}") } activity.startActivity(intent) } catch (e: Exception) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.parse("package:${activity.packageName}") } activity.startActivity(intent) } return } } // 모든 권한이 허용된 경우 플로우 완료 isPermissionFlowActive = false } fun requestBatteryOptimization(context: Context) { try { val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { data = Uri.parse("package:${context.packageName}") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } catch (e: Exception) { try { val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } catch (e2: Exception) { try { // Samsung Galaxy Device Care Battery Activity val intent = Intent().apply { setClassName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } catch (e3: Exception) { try { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.parse("package:${context.packageName}") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } catch (e4: Exception) { Toast.makeText(context, "배터리 설정 화면을 열 수 없습니다.", Toast.LENGTH_SHORT).show() } } } } } fun requestOverlayPermission(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply { data = Uri.parse("package:${context.packageName}") } if (context !is Activity) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } } fun requestFullScreenIntentPermission(context: Context) { if (Build.VERSION.SDK_INT >= 34) { try { val intent = Intent("android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT").apply { data = Uri.parse("package:${context.packageName}") } if (context !is Activity) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } catch (e: Exception) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.parse("package:${context.packageName}") } if (context !is Activity) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) } } } fun isAllPermissionsGranted(context: Context): Boolean { var allGranted = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { allGranted = allGranted && ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager allGranted = allGranted && am.canScheduleExactAlarms() } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager allGranted = allGranted && pm.isIgnoringBatteryOptimizations(context.packageName) } if (Build.VERSION.SDK_INT >= 34) { val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager allGranted = allGranted && nm.canUseFullScreenIntent() } return allGranted } fun getBatteryOptimizationStatus(context: Context): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager return pm.isIgnoringBatteryOptimizations(context.packageName) } return true } fun getExactAlarmStatus(context: Context): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager return am.canScheduleExactAlarms() } return true } fun getOverlayStatus(context: Context): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return Settings.canDrawOverlays(context) } return true } fun getFullScreenIntentStatus(context: Context): Boolean { if (Build.VERSION.SDK_INT >= 34) { val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager return nm.canUseFullScreenIntent() } return true } }