feat: 권한 설정 UI 개선 및 APK 다운로드 버그 수정
- 설정 화면에 리마인더 및 알 수 없는 앱 설치 권한 체크 추가 - PermissionHelper에 canInstallUnknownApps, openUnknownAppsSettings 추가 - APK 다운로드 BroadcastReceiver 사용하도록 개선 - 다운로드 실패 처리 및 진행률 개선
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.hotdeal.alarm.presentation.main
|
package com.hotdeal.alarm.presentation.main
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
@@ -8,8 +9,11 @@ import androidx.activity.enableEdgeToEdge
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.hotdeal.alarm.ui.theme.HotDealTheme
|
import com.hotdeal.alarm.ui.theme.HotDealTheme
|
||||||
@@ -27,11 +31,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var workerScheduler: WorkerScheduler
|
lateinit var workerScheduler: WorkerScheduler
|
||||||
|
|
||||||
|
private var downloadReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
// 2분 간격으로 폴링 시작
|
// 2분 간격으로 폴<EFBFBD> 시작
|
||||||
workerScheduler.schedulePeriodicPolling(2)
|
workerScheduler.schedulePeriodicPolling(2)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
@@ -47,6 +53,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
var showUpdateDialog by remember { mutableStateOf(false) }
|
var showUpdateDialog by remember { mutableStateOf(false) }
|
||||||
var downloadProgress by remember { mutableStateOf(0) }
|
var downloadProgress by remember { mutableStateOf(0) }
|
||||||
var isDownloading by remember { mutableStateOf(false) }
|
var isDownloading by remember { mutableStateOf(false) }
|
||||||
|
var downloadId by remember { mutableStateOf(-1L) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
checkForUpdate { info ->
|
checkForUpdate { info ->
|
||||||
@@ -69,27 +76,41 @@ class MainActivity : ComponentActivity() {
|
|||||||
onUpdate = {
|
onUpdate = {
|
||||||
// APK 다이렉트 다운로드
|
// APK 다이렉트 다운로드
|
||||||
isDownloading = true
|
isDownloading = true
|
||||||
val downloadId = ApkDownloadManager.downloadApk(
|
downloadId = ApkDownloadManager.downloadApk(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
updateInfo!!
|
updateInfo!!
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 다운로드 완료 리시버 등록
|
||||||
|
downloadReceiver = ApkDownloadManager.registerDownloadCompleteReceiver(
|
||||||
|
context = this@MainActivity,
|
||||||
|
downloadId = downloadId,
|
||||||
|
onComplete = {
|
||||||
|
isDownloading = false
|
||||||
|
showUpdateDialog = false
|
||||||
|
ApkDownloadManager.installApk(this@MainActivity)
|
||||||
|
},
|
||||||
|
onFailed = {
|
||||||
|
isDownloading = false
|
||||||
|
Toast.makeText(
|
||||||
|
this@MainActivity,
|
||||||
|
"다운로드 실패. 다시 시도해주세요.",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 다운로드 진행률 모니터링
|
// 다운로드 진행률 모니터링
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
while (isDownloading) {
|
while (isDownloading) {
|
||||||
downloadProgress = ApkDownloadManager.getDownloadProgress(
|
val status = ApkDownloadManager.getDownloadStatus(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
downloadId
|
downloadId
|
||||||
)
|
)
|
||||||
|
downloadProgress = status.progress
|
||||||
|
|
||||||
if (ApkDownloadManager.isDownloadComplete(
|
if (status.isComplete || status.isFailed) {
|
||||||
this@MainActivity,
|
break
|
||||||
downloadId
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
isDownloading = false
|
|
||||||
showUpdateDialog = false
|
|
||||||
ApkDownloadManager.installApk(this@MainActivity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinx.coroutines.delay(500)
|
kotlinx.coroutines.delay(500)
|
||||||
@@ -103,6 +124,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
// 리시버 해제
|
||||||
|
downloadReceiver?.let {
|
||||||
|
ApkDownloadManager.unregisterDownloadCompleteReceiver(this, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 업데이트 체크
|
* 업데이트 체크
|
||||||
*/
|
*/
|
||||||
@@ -159,7 +188,7 @@ fun UpdateDialog(
|
|||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = downloadProgress / 100f,
|
progress = { downloadProgress / 100f },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ private fun NotificationSettingsHeader(
|
|||||||
onRequestPermission: () -> Unit,
|
onRequestPermission: () -> Unit,
|
||||||
onOpenSystemSettings: () -> Unit
|
onOpenSystemSettings: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
@@ -260,40 +262,84 @@ private fun NotificationSettingsHeader(
|
|||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// 알림 권한 상태
|
// 알림 권한 상태
|
||||||
NotificationStatusRow(
|
PermissionStatusRow(
|
||||||
icon = if (permissionStatus.hasNotificationPermission) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
icon = if (permissionStatus.hasNotificationPermission) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
||||||
title = if (permissionStatus.hasNotificationPermission) "알림 권한 허용됨" else "알림 권한 필요",
|
title = "알림 권한",
|
||||||
|
description = if (permissionStatus.hasNotificationPermission) "허용됨" else "필요함",
|
||||||
isOk = permissionStatus.hasNotificationPermission,
|
isOk = permissionStatus.hasNotificationPermission,
|
||||||
action = if (!permissionStatus.hasNotificationPermission) {
|
onAction = if (!permissionStatus.hasNotificationPermission) {
|
||||||
{ onRequestPermission() }
|
{ onRequestPermission() }
|
||||||
} else null,
|
} else null,
|
||||||
actionLabel = "권한 허용",
|
actionLabel = "허용"
|
||||||
secondaryAction = if (permissionStatus.hasNotificationPermission) {
|
|
||||||
{ onOpenSystemSettings() }
|
|
||||||
} else null,
|
|
||||||
secondaryActionLabel = "시스템 설정"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 정확한 알람 (리마인더) 권한 상태
|
||||||
|
PermissionStatusRow(
|
||||||
|
icon = if (permissionStatus.hasExactAlarmPermission) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
||||||
|
title = "리마인더 및 알람",
|
||||||
|
description = if (permissionStatus.hasExactAlarmPermission) "허용됨" else "정확한 시간 알림에 필요",
|
||||||
|
isOk = permissionStatus.hasExactAlarmPermission,
|
||||||
|
onAction = if (!permissionStatus.hasExactAlarmPermission) {
|
||||||
|
{ PermissionHelper.openExactAlarmSettings(context) }
|
||||||
|
} else null,
|
||||||
|
actionLabel = "설정"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 알 수 없는 앱 설치 권한 상태
|
||||||
|
PermissionStatusRow(
|
||||||
|
icon = if (permissionStatus.canInstallUnknownApps) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
||||||
|
title = "앱 설치 권한",
|
||||||
|
description = if (permissionStatus.canInstallUnknownApps) "허용됨" else "업데이트 설치에 필요",
|
||||||
|
isOk = permissionStatus.canInstallUnknownApps,
|
||||||
|
onAction = if (!permissionStatus.canInstallUnknownApps) {
|
||||||
|
{ PermissionHelper.openUnknownAppsSettings(context) }
|
||||||
|
} else null,
|
||||||
|
actionLabel = "설정"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// 사이트 선택 상태
|
// 사이트 선택 상태
|
||||||
NotificationStatusRow(
|
PermissionStatusRow(
|
||||||
icon = if (hasEnabledSites) Icons.Filled.CheckCircle else Icons.Filled.Error,
|
icon = if (hasEnabledSites) Icons.Filled.CheckCircle else Icons.Filled.Error,
|
||||||
title = if (hasEnabledSites) "사이트 선택 완료" else "사이트 선택 필요",
|
title = "사이트 선택",
|
||||||
|
description = if (hasEnabledSites) "완료" else "최소 1개 이상 필요",
|
||||||
isOk = hasEnabledSites
|
isOk = hasEnabledSites
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 시스템 설정 버튼
|
||||||
|
if (permissionStatus.hasNotificationPermission || permissionStatus.hasExactAlarmPermission || permissionStatus.canInstallUnknownApps) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { PermissionHelper.openAppSettings(context) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Settings,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("시스템 설정 열기")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NotificationStatusRow(
|
private fun PermissionStatusRow(
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
title: String,
|
title: String,
|
||||||
|
description: String,
|
||||||
isOk: Boolean,
|
isOk: Boolean,
|
||||||
action: (() -> Unit)? = null,
|
onAction: (() -> Unit)? = null,
|
||||||
actionLabel: String = "",
|
actionLabel: String = ""
|
||||||
secondaryAction: (() -> Unit)? = null,
|
|
||||||
secondaryActionLabel: String = ""
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -306,25 +352,25 @@ private fun NotificationStatusRow(
|
|||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
)
|
||||||
if (action != null) {
|
Text(
|
||||||
TextButton(onClick = action) {
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onAction != null) {
|
||||||
|
TextButton(onClick = onAction) {
|
||||||
Text(actionLabel, style = MaterialTheme.typography.labelMedium)
|
Text(actionLabel, style = MaterialTheme.typography.labelMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (secondaryAction != null) {
|
|
||||||
TextButton(onClick = secondaryAction) {
|
|
||||||
Text(secondaryActionLabel, style = MaterialTheme.typography.labelMedium)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SectionHeader(
|
private fun SectionHeader(
|
||||||
title: String,
|
title: String,
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
package com.hotdeal.alarm.util
|
package com.hotdeal.alarm.util
|
||||||
|
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +41,8 @@ object ApkDownloadManager {
|
|||||||
setAllowedOverMetered(true)
|
setAllowedOverMetered(true)
|
||||||
setAllowedOverRoaming(true)
|
setAllowedOverRoaming(true)
|
||||||
setMimeType("application/vnd.android.package-archive")
|
setMimeType("application/vnd.android.package-archive")
|
||||||
|
// Wi-Fi 환경에서 다운로드 우선
|
||||||
|
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadId = downloadManager.enqueue(request)
|
val downloadId = downloadManager.enqueue(request)
|
||||||
@@ -49,6 +56,106 @@ object ApkDownloadManager {
|
|||||||
return downloadId
|
return downloadId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다운로드 완료 리시버 등록
|
||||||
|
*/
|
||||||
|
fun registerDownloadCompleteReceiver(
|
||||||
|
context: Context,
|
||||||
|
downloadId: Long,
|
||||||
|
onComplete: () -> Unit,
|
||||||
|
onFailed: () -> Unit
|
||||||
|
): BroadcastReceiver {
|
||||||
|
val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1
|
||||||
|
if (id == downloadId) {
|
||||||
|
val downloadManager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager
|
||||||
|
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||||
|
val cursor = downloadManager?.query(query)
|
||||||
|
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val statusIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||||
|
val status = it.getInt(statusIndex)
|
||||||
|
|
||||||
|
when (status) {
|
||||||
|
DownloadManager.STATUS_SUCCESSFUL -> onComplete()
|
||||||
|
DownloadManager.STATUS_FAILED -> onFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.registerReceiver(
|
||||||
|
receiver,
|
||||||
|
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
|
)
|
||||||
|
|
||||||
|
return receiver
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다운로드 완료 리시버 해제
|
||||||
|
*/
|
||||||
|
fun unregisterDownloadCompleteReceiver(context: Context, receiver: BroadcastReceiver) {
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(receiver)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 이미 해제된 경우 무시
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다운로드 상태 확인 (suspend 함수)
|
||||||
|
*/
|
||||||
|
suspend fun getDownloadStatus(context: Context, downloadId: Long): DownloadStatus =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||||
|
val cursor = downloadManager.query(query)
|
||||||
|
|
||||||
|
var status = DownloadStatus(0, 0, false, false)
|
||||||
|
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val statusIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||||
|
val bytesDownloadedIndex = it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
||||||
|
val bytesTotalIndex = it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
||||||
|
|
||||||
|
val downloadStatus = it.getInt(statusIndex)
|
||||||
|
val bytesDownloaded = it.getLong(bytesDownloadedIndex)
|
||||||
|
val bytesTotal = it.getLong(bytesTotalIndex)
|
||||||
|
|
||||||
|
val progress = if (bytesTotal > 0) {
|
||||||
|
((bytesDownloaded * 100) / bytesTotal).toInt()
|
||||||
|
} else 0
|
||||||
|
|
||||||
|
status = DownloadStatus(
|
||||||
|
progress = progress,
|
||||||
|
bytesDownloaded = bytesDownloaded,
|
||||||
|
isComplete = downloadStatus == DownloadManager.STATUS_SUCCESSFUL,
|
||||||
|
isFailed = downloadStatus == DownloadManager.STATUS_FAILED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다운로드 완료 대기 (suspend 함수)
|
||||||
|
*/
|
||||||
|
suspend fun waitForDownload(context: Context, downloadId: Long): Boolean {
|
||||||
|
while (true) {
|
||||||
|
val status = getDownloadStatus(context, downloadId)
|
||||||
|
if (status.isComplete) return true
|
||||||
|
if (status.isFailed) return false
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APK 파일 설치
|
* APK 파일 설치
|
||||||
*/
|
*/
|
||||||
@@ -80,45 +187,12 @@ object ApkDownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 다운로드 완료 여부 확인
|
* 다운로드 상태 데이터 클래스
|
||||||
*/
|
*/
|
||||||
fun isDownloadComplete(context: Context, downloadId: Long): Boolean {
|
data class DownloadStatus(
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val progress: Int,
|
||||||
val query = DownloadManager.Query().setFilterById(downloadId)
|
val bytesDownloaded: Long,
|
||||||
|
val isComplete: Boolean,
|
||||||
val cursor = downloadManager.query(query)
|
val isFailed: Boolean
|
||||||
if (cursor.moveToFirst()) {
|
)
|
||||||
val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
|
||||||
val status = cursor.getInt(statusIndex)
|
|
||||||
cursor.close()
|
|
||||||
return status == DownloadManager.STATUS_SUCCESSFUL
|
|
||||||
}
|
|
||||||
cursor.close()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 다운로드 진행률 가져오기
|
|
||||||
*/
|
|
||||||
fun getDownloadProgress(context: Context, downloadId: Long): Int {
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
||||||
val query = DownloadManager.Query().setFilterById(downloadId)
|
|
||||||
|
|
||||||
val cursor = downloadManager.query(query)
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
val bytesDownloadedIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
|
||||||
val bytesTotalIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
|
||||||
|
|
||||||
val bytesDownloaded = cursor.getLong(bytesDownloadedIndex)
|
|
||||||
val bytesTotal = cursor.getLong(bytesTotalIndex)
|
|
||||||
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
if (bytesTotal > 0) {
|
|
||||||
return ((bytesDownloaded * 100) / bytesTotal).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor.close()
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,17 @@ object PermissionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알 수 없는 앱 설치 권한이 있는지 확인
|
||||||
|
*/
|
||||||
|
fun canInstallUnknownApps(context: Context): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.packageManager.canRequestPackageInstalls()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 정확한 알람 설정 화면 열기
|
* 정확한 알람 설정 화면 열기
|
||||||
*/
|
*/
|
||||||
@@ -58,6 +69,22 @@ object PermissionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알 수 없는 앱 설치 설정 화면 열기
|
||||||
|
*/
|
||||||
|
fun openUnknownAppsSettings(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
openAppSettings(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 앱 설정 화면 열기
|
* 앱 설정 화면 열기
|
||||||
*/
|
*/
|
||||||
@@ -90,6 +117,7 @@ object PermissionHelper {
|
|||||||
data class PermissionStatus(
|
data class PermissionStatus(
|
||||||
val hasNotificationPermission: Boolean,
|
val hasNotificationPermission: Boolean,
|
||||||
val hasExactAlarmPermission: Boolean,
|
val hasExactAlarmPermission: Boolean,
|
||||||
|
val canInstallUnknownApps: Boolean,
|
||||||
val isAllGranted: Boolean
|
val isAllGranted: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,11 +127,13 @@ object PermissionHelper {
|
|||||||
fun checkAllPermissions(context: Context): PermissionStatus {
|
fun checkAllPermissions(context: Context): PermissionStatus {
|
||||||
val hasNotification = hasNotificationPermission(context)
|
val hasNotification = hasNotificationPermission(context)
|
||||||
val hasExactAlarm = hasExactAlarmPermission(context)
|
val hasExactAlarm = hasExactAlarmPermission(context)
|
||||||
|
val canInstallUnknown = canInstallUnknownApps(context)
|
||||||
|
|
||||||
return PermissionStatus(
|
return PermissionStatus(
|
||||||
hasNotificationPermission = hasNotification,
|
hasNotificationPermission = hasNotification,
|
||||||
hasExactAlarmPermission = hasExactAlarm,
|
hasExactAlarmPermission = hasExactAlarm,
|
||||||
isAllGranted = hasNotification && hasExactAlarm
|
canInstallUnknownApps = canInstallUnknown,
|
||||||
|
isAllGranted = hasNotification && hasExactAlarm && canInstallUnknown
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user