332 lines
14 KiB
Kotlin
332 lines
14 KiB
Kotlin
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<String>()
|
|
|
|
// 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<String>) {
|
|
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
|
|
}
|
|
}
|