Coverage Summary for Class: LogxPathResolver (kr.open.library.simple_ui.core.logcat.internal.common)

Class Method, % Branch, % Line, % Instruction, %
LogxPathResolver 85.7% (6/7) 36.8% (7/19) 58.6% (17/29) 45.3% (68/150)
LogxPathResolver$WhenMappings
Total 85.7% (6/7) 36.8% (7/19) 58.6% (17/29) 45.3% (68/150)


 package kr.open.library.simple_ui.core.logcat.internal.common
 
 import android.Manifest
 import android.content.Context
 import android.content.pm.PackageManager
 import android.os.Build
 import android.os.Environment
 import androidx.core.content.ContextCompat
 import kr.open.library.simple_ui.core.extensions.conditional.checkSdkVersion
 import kr.open.library.simple_ui.core.logcat.config.LogStorageType
 import java.io.File
 
 /**
  * Resolves log directory paths for different storage types.<br><br>
  * 저장소 타입별 로그 디렉터리 경로를 계산한다.<br>
  */
 internal object LogxPathResolver {
     /**
      * Resolves absolute path for the given storage type.<br><br>
      * 지정된 저장소 타입의 절대 경로를 반환한다.<br>
      *
      * @param context Application context.<br><br>
      *                애플리케이션 컨텍스트.<br>
      * @param storageType Storage target.<br><br>
      *                    저장소 대상.<br>
      */
     fun resolvePath(context: Context, storageType: LogStorageType): String = when (storageType) {
         LogStorageType.INTERNAL -> getInternalLogPath(context)
         LogStorageType.APP_EXTERNAL -> getAppExternalLogPath(context)
         LogStorageType.PUBLIC_EXTERNAL -> getPublicExternalLogPath(context)
     }
 
     /**
      * Returns whether the given storage type requires runtime permission.<br><br>
      * 지정된 저장소 타입이 런타임 권한을 요구하는지 반환한다.<br>
      *
      * @param storageType Storage target.<br><br>
      *                    저장소 대상.<br>
      */
     fun requiresPermission(storageType: LogStorageType): Boolean = checkSdkVersion(Build.VERSION_CODES.Q,
         positiveWork = { false },
         negativeWork = { storageType == LogStorageType.PUBLIC_EXTERNAL }
     )
 
     /**
      * Checks WRITE_EXTERNAL_STORAGE permission if required.<br><br>
      * 필요한 경우 WRITE_EXTERNAL_STORAGE 권한 보유 여부를 확인한다.<br>
      *
      * @param context Application context.<br><br>
      *                애플리케이션 컨텍스트.<br>
      * @param storageType Storage target.<br><br>
      *                    저장소 대상.<br>
      */
     fun hasWritePermission(context: Context, storageType: LogStorageType): Boolean {
         if (!requiresPermission(storageType)) return true
         return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
     }
 
     /**
      * Returns whether a custom directory path is supported for file logging on the current platform.<br><br>
      * 현재 플랫폼에서 사용자 지정 디렉터리 경로를 파일 로그 저장용으로 지원하는지 반환합니다.<br>
      *
      * On Android 10+ only app-internal or app-specific external directories are supported for
      * direct file writes without SAF/MediaStore.<br><br>
      * Android 10+에서는 SAF/MediaStore 없이 직접 파일 쓰기를 수행할 수 있는 앱 내부 또는
      * 앱 전용 외부 디렉터리만 지원합니다.<br>
      *
      * @param context Application context.<br><br>
      *                애플리케이션 컨텍스트.<br>
      * @param directory Candidate custom directory.<br><br>
      *                  확인할 사용자 지정 디렉터리 후보입니다.<br>
      */
     fun isSupportedCustomDirectory(context: Context, directory: File): Boolean {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true
 
         val candidatePath = directory.absolutePath
         val allowedRoots = buildList {
             add(context.filesDir.absolutePath)
             context
                 .getExternalFilesDirs(null)
                 .mapNotNull { it?.absolutePath }
                 .forEach { add(it) }
         }
 
         return allowedRoots.any { root -> candidatePath == root || candidatePath.startsWith(root + File.separator) }
     }
 
     /**
      * Returns internal storage log path.<br><br>
      * 내부 저장소 로그 경로를 반환한다.<br>
      */
     private fun getInternalLogPath(context: Context): String = context.filesDir.absolutePath + "/${LogxConstants.LOG_DIR_NAME}"
 
     /**
      * Returns app-specific external storage log path.<br><br>
      * 앱 전용 외부 저장소 로그 경로를 반환한다.<br>
      */
     private fun getAppExternalLogPath(context: Context): String =
         context.getExternalFilesDir(LogxConstants.LOG_DIR_NAME)?.absolutePath ?: getInternalLogPath(context)
 
     /**
      * Returns the log path for [LogStorageType.PUBLIC_EXTERNAL].<br>
      * Uses true public external storage on API 28 and below, and falls back to the app-specific
      * external Documents directory on API 29+ due to scoped storage.<br>
      * If the API 29+ Documents directory is unavailable, it falls back again to the app-specific
      * external log path.<br><br>
      * [LogStorageType.PUBLIC_EXTERNAL]용 로그 경로를 반환합니다.<br>
      * API 28 이하에서는 실제 공용 외부 저장소를 사용하고, API 29+에서는 Scoped Storage 정책에 따라
      * 앱 전용 외부 Documents 디렉터리로 대체됩니다.<br>
      * API 29+에서 Documents 디렉터리를 사용할 수 없으면 앱 전용 외부 로그 경로로 한 번 더 대체됩니다.<br>
      */
     private fun getPublicExternalLogPath(context: Context): String = checkSdkVersion(
         Build.VERSION_CODES.Q,
         positiveWork = {
             val documentsDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
             if (documentsDir != null) {
                 documentsDir.absolutePath + "/${LogxConstants.LOG_DIR_NAME}"
             } else {
                 getAppExternalLogPath(context)
             }
         },
         negativeWork = {
             @Suppress("DEPRECATION")
             Environment.getExternalStorageDirectory().absolutePath + "/${LogxConstants.LOG_DIR_NAME}"
         },
     )
 }