Coverage Summary for Class: PermissionExtensionsKt (kr.open.library.simple_ui.core.permissions.extensions)

Class Class, % Method, % Branch, % Line, % Instruction, %
PermissionExtensionsKt 100% (1/1) 83.3% (10/12) 62.3% (38/61) 83.8% (57/68) 70.9% (344/485)


 /**
  * Permission inspection helpers that unify special-case checks (usage stats, overlays, alarms, etc.).<br><br>
  * 사용량 통계·오버레이·정확 알람 등 특수 권한을 한 번에 점검할 수 있는 보조 함수 모음입니다.<br>
  */
 package kr.open.library.simple_ui.core.permissions.extensions
 
 import android.Manifest
 import android.app.AlarmManager
 import android.app.AppOpsManager
 import android.app.NotificationManager
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.PermissionInfo
 import android.os.Build
 import android.os.Environment
 import android.os.PowerManager
 import android.os.Process
 import android.provider.Settings
 import androidx.core.app.NotificationManagerCompat
 import androidx.core.content.ContextCompat
 import androidx.core.content.pm.PermissionInfoCompat
 import kr.open.library.simple_ui.core.extensions.conditional.checkSdkVersion
 import kr.open.library.simple_ui.core.extensions.trycatch.safeCatch
 import kr.open.library.simple_ui.core.permissions.vo.PermissionSpecialType
 
 /**
  * Evaluates whether the caller already holds [permission], including platform-specific toggles.<br>
  * Dangerous permissions are checked via runtime APIs, and normal permissions are treated as granted by design.<br>
  * Signature/privileged-style permissions are not considered granted for ordinary app processes.<br><br>
  * 필요한 플랫폼 토글 여부까지 확인하여 [permission] 권한을 보유했는지 판단합니다.<br>
  * 런타임 권한은 실제 검사하고, 일반 권한은 설계상 허용된 것으로 간주합니다.<br>
  * 서명/특권 계열 권한은 일반 앱 프로세스에서 허용된 것으로 취급하지 않습니다.<br>
  *
  * @param permission Android permission string being evaluated.<br><br>
  *        확인할 Android 권한 문자열입니다.<br>
  * @return true when the permission (or equivalent toggle) is granted, otherwise false.<br><br>
  *         권한 또는 동등한 토글이 허용되면 true, 그렇지 않으면 false입니다.<br>
  */
 public inline fun Context.hasPermission(permission: String): Boolean = when (permission) {
     Manifest.permission.SYSTEM_ALERT_WINDOW -> Settings.canDrawOverlays(this)
 
     Manifest.permission.WRITE_SETTINGS -> Settings.System.canWrite(this)
 
     Manifest.permission.PACKAGE_USAGE_STATS -> hasUsageStatsPermission()
 
     Manifest.permission.BIND_ACCESSIBILITY_SERVICE -> hasAccessibilityServicePermission()
 
     Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE -> hasNotificationListenerPermission()
 
     Manifest.permission.MANAGE_EXTERNAL_STORAGE -> checkSdkVersion(Build.VERSION_CODES.R,
         positiveWork = { Environment.isExternalStorageManager() },
         negativeWork = {
             ContextCompat.checkSelfPermission(
                 this,
                 Manifest.permission.WRITE_EXTERNAL_STORAGE
             ) == PackageManager.PERMISSION_GRANTED
         },
     )
 
     Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> {
         val powerManager = getSystemService(Context.POWER_SERVICE) as? PowerManager
         powerManager?.isIgnoringBatteryOptimizations(packageName) ?: false
     }
 
     Manifest.permission.SCHEDULE_EXACT_ALARM -> checkSdkVersion(Build.VERSION_CODES.S,
         positiveWork = {
             val alarmManager = getSystemService(Context.ALARM_SERVICE) as? AlarmManager
             alarmManager?.canScheduleExactAlarms() ?: false
         },
         negativeWork = { true },
     )
 
     Manifest.permission.POST_NOTIFICATIONS -> checkSdkVersion(Build.VERSION_CODES.TIRAMISU,
         positiveWork = { NotificationManagerCompat.from(this).areNotificationsEnabled() },
         negativeWork = { true },
     )
 
     Manifest.permission.REQUEST_INSTALL_PACKAGES -> checkSdkVersion(Build.VERSION_CODES.O,
         positiveWork = { packageManager.canRequestPackageInstalls() },
         negativeWork = { true },
     )
 
     Manifest.permission.ACCESS_NOTIFICATION_POLICY -> {
         val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
         notificationManager?.isNotificationPolicyAccessGranted ?: false
     }
 
     else -> {
         when (getPermissionBaseProtectionLevel(permission)) {
             PermissionInfo.PROTECTION_DANGEROUS ->
                 ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
 
             PermissionInfo.PROTECTION_NORMAL -> true
 
             else -> false
         }
     }
 }
 
 /**
  * Returns true only when every entry in [permissions] is granted.<br>
  * Normal permissions are treated as granted by design.<br><br>
  * [permissions]의 모든 항목이 허용되었을 때만 true를 반환합니다.<br>
  * 일반 권한은 설계상 허용된 것으로 간주합니다.<br>
  *
  * @param permissions List of permission strings to verify.<br><br>
  *        확인할 권한 문자열 목록입니다.<br>
  * @return true when all permissions are granted.<br><br>
  *         모든 권한이 허용되면 true입니다.<br>
  */
 public inline fun Context.hasPermissions(vararg permissions: String): Boolean = permissions.all { permission -> hasPermission(permission) }
 
 /**
  * Executes [doWork] only when every permission in [permissions] is granted.<br>
  * Normal permissions are treated as granted by design.<br><br>
  * [permissions]에 포함된 권한이 모두 허용된 경우에만 [doWork]를 실행합니다.<br>
  * 일반 권한은 설계상 허용된 것으로 간주합니다.<br>
  *
  * @param permissions Permission strings to validate.<br><br>
  *        검증할 권한 문자열 목록입니다.<br>
  * @param doWork Action executed after the permissions are confirmed.<br><br>
  *        권한 확인 후 수행할 동작입니다.<br>
  * @return true when [doWork] ran because every permission was granted.<br><br>
  *         모든 권한이 허용되어 [doWork]가 실행되면 true입니다.<br>
  */
 public inline fun Context.hasPermissions(vararg permissions: String, doWork: () -> Unit): Boolean =
     if (permissions.all { permission -> hasPermission(permission) }) {
         doWork()
         true
     } else {
         false
     }
 
 /**
  * Produces a list of permissions from [permissions] that are still missing.<br>
  * Dangerous permissions are checked explicitly, and normal permissions are treated as granted by design.<br><br>
  * [permissions] 중 아직 허용되지 않은 권한 목록을 반환합니다.<br>
  * 위험 권한은 실제 검사하고, 일반 권한은 설계상 허용된 것으로 간주합니다.<br>
  *
  * @param permissions Requested permission list.<br><br>
  *        요청할 권한 목록입니다.<br>
  * @return Permissions that have not been granted yet.<br><br>
  *         아직 허용되지 않은 권한 목록입니다.<br>
  */
 public inline fun Context.remainPermissions(permissions: List<String>): List<String> = permissions.filterNot { hasPermission(it) }
 
 /**
  * Checks whether usage stats access is enabled for this app.<br><br>
  * 앱에 사용량 통계 권한이 허용되었는지 확인합니다.<br>
  *
  * @return true when usage stats access is granted, otherwise false.<br><br>
  *         사용량 통계 권한이 허용되면 true, 아니면 false입니다.<br>
  */
 public inline fun Context.hasUsageStatsPermission(): Boolean = safeCatch(defaultValue = false) {
     val appOps = getSystemService(Context.APP_OPS_SERVICE) as? AppOpsManager
     appOps?.checkOpNoThrow(
         AppOpsManager.OPSTR_GET_USAGE_STATS,
         Process.myUid(),
         packageName,
     ) == AppOpsManager.MODE_ALLOWED
 }
 
 /**
  * Checks whether at least one accessibility service from this package is enabled.<br><br>
  * 이 패키지의 접근성 서비스가 활성화되어 있는지 확인합니다.<br>
  *
  * @return true when the package appears in enabled accessibility services.<br><br>
  *         접근성 서비스 활성 목록에 패키지가 있으면 true입니다.<br>
  */
 public inline fun Context.hasAccessibilityServicePermission(): Boolean = safeCatch(defaultValue = false) {
     val enabledServices = Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,)
     enabledServices?.split(":")?.any { it.split("/").firstOrNull() == packageName } == true
 }
 
 /**
  * Checks whether a specific accessibility service component is enabled.<br><br>
  * 특정 접근성 서비스 컴포넌트가 활성화되어 있는지 확인합니다.<br>
  *
  * **Note / 주의:** This overload is a standalone utility and is **not** connected to the
  * `PermissionRequester` flow. The internal special-permission grant check (`SpecialPermissionHandler`)
  * still uses the package-level overload [hasAccessibilityServicePermission].
  * Use this overload directly when component-level precision is needed outside the permission flow.<br><br>
  * 이 오버로드는 독립 유틸리티이며 `PermissionRequester` 흐름과 **연결되지 않습니다**.
  * 내부 특수 권한 허용 판정(`SpecialPermissionHandler`)은 여전히 패키지 단위 오버로드를 사용합니다.
  * 권한 흐름 외부에서 컴포넌트 단위 정밀 확인이 필요한 경우 이 오버로드를 직접 사용하세요.<br>
  *
  * @param componentName The ComponentName of the accessibility service to check.<br><br>
  *        확인할 접근성 서비스의 ComponentName입니다.<br>
  * @return true when the exact component appears in enabled accessibility services.<br><br>
  *         정확한 컴포넌트가 접근성 서비스 활성 목록에 있으면 true입니다.<br>
  */
 public inline fun Context.hasAccessibilityServicePermission(componentName: ComponentName): Boolean = safeCatch(defaultValue = false) {
     val enabledServices = Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
     enabledServices?.split(":")?.any {
         ComponentName.unflattenFromString(it) == componentName
     } == true
 }
 
 /**
  * Checks whether the app is listed as a notification listener.<br><br>
  * 앱이 알림 리스너로 등록되어 있는지 확인합니다.<br>
  *
  * @return true when the package is present in the enabled listener list.<br><br>
  *         활성 알림 리스너 목록에 패키지가 있으면 true입니다.<br>
  */
 public inline fun Context.hasNotificationListenerPermission(): Boolean = safeCatch(defaultValue = false) {
     val enabledListeners = Settings.Secure.getString(contentResolver, "enabled_notification_listeners",)
     enabledListeners?.split(":")?.any { it.split("/").firstOrNull() == packageName } == true
 }
 
 /**
  * Checks whether a specific notification listener component is enabled.<br><br>
  * 특정 알림 리스너 컴포넌트가 활성화되어 있는지 확인합니다.<br>
  *
  * **Note / 주의:** This overload is a standalone utility and is **not** connected to the
  * `PermissionRequester` flow. The internal special-permission grant check (`SpecialPermissionHandler`)
  * still uses the package-level overload [hasNotificationListenerPermission].
  * Use this overload directly when component-level precision is needed outside the permission flow.<br><br>
  * 이 오버로드는 독립 유틸리티이며 `PermissionRequester` 흐름과 **연결되지 않습니다**.
  * 내부 특수 권한 허용 판정(`SpecialPermissionHandler`)은 여전히 패키지 단위 오버로드를 사용합니다.
  * 권한 흐름 외부에서 컴포넌트 단위 정밀 확인이 필요한 경우 이 오버로드를 직접 사용하세요.<br>
  *
  * @param componentName The ComponentName of the notification listener service to check.<br><br>
  *        확인할 알림 리스너 서비스의 ComponentName입니다.<br>
  * @return true when the exact component appears in the enabled listener list.<br><br>
  *         정확한 컴포넌트가 활성 알림 리스너 목록에 있으면 true입니다.<br>
  */
 public inline fun Context.hasNotificationListenerPermission(componentName: ComponentName): Boolean = safeCatch(defaultValue = false) {
     val enabledListeners = Settings.Secure.getString(contentResolver, "enabled_notification_listeners")
     enabledListeners?.split(":")?.any {
         ComponentName.unflattenFromString(it) == componentName
     } == true
 }
 
 /**
  * Determines whether [permission] is one of the framework-defined special cases.<br><br>
  * [permission]이 프레임워크에서 정의한 특수 권한인지 판별합니다.<br>
  *
  * @param permission Permission string to classify.<br><br>
  *        분류할 권한 문자열입니다.<br>
  * @return true when the permission matches [PermissionSpecialType].<br><br>
  *         [PermissionSpecialType] 목록 중 하나와 일치하면 true입니다.<br>
  */
 public inline fun isSpecialPermission(permission: String): Boolean {
     PermissionSpecialType.entries.forEach {
         if (permission == it.permission) return true
     }
     return false
 }
 
 /**
  * Reads the raw platform protection value for [permission].<br><br>
  * [permission] 권한의 원본 플랫폼 보호 수준 값을 조회합니다.<br>
  *
  * @param permission Permission string to query.<br><br>
  *        조회할 권한 문자열입니다.<br>
  * @return Protection level constant, defaulting to [PermissionInfo.PROTECTION_DANGEROUS] on lookup failure.<br><br>
  *         보호 수준 상수이며 조회 실패 시 기본값은 [PermissionInfo.PROTECTION_DANGEROUS]입니다.<br>
  */
 public inline fun Context.getPermissionProtectionLevel(permission: String): Int =
     safeCatch(defaultValue = PermissionInfo.PROTECTION_DANGEROUS) {
         packageManager.getPermissionInfo(permission, 0).protection
     }
 
 /**
  * Reads only the base protection level for [permission], excluding protection flags.<br><br>
  * [permission] 권한의 보호 플래그를 제외한 기본 보호 수준만 조회합니다.<br>
  *
  * @param permission Permission string to query.<br><br>
  *        조회할 권한 문자열입니다.<br>
  * @return Base protection level constant, defaulting to [PermissionInfo.PROTECTION_DANGEROUS] on lookup failure.<br><br>
  *         기본 보호 수준 상수이며 조회 실패 시 기본값은 [PermissionInfo.PROTECTION_DANGEROUS]입니다.<br>
  */
 public inline fun Context.getPermissionBaseProtectionLevel(permission: String): Int =
     safeCatch(defaultValue = PermissionInfo.PROTECTION_DANGEROUS) {
         val permissionInfo = packageManager.getPermissionInfo(permission, 0)
         PermissionInfoCompat.getProtection(permissionInfo)
     }