6a0758ffad
Копирование ringtone во внутреннее хранилище, FileProvider, каналы с hash и миграция URI.
110 lines
4.2 KiB
Kotlin
110 lines
4.2 KiB
Kotlin
package ru.kalinamall.seaca.push
|
|
|
|
import android.content.ContentResolver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.media.RingtoneManager
|
|
import android.net.Uri
|
|
import androidx.core.content.FileProvider
|
|
import ru.kalinamall.seaca.data.NotificationSoundSettings
|
|
import ru.kalinamall.seaca.data.NotificationSoundSource
|
|
import ru.kalinamall.seaca.data.SessionStore
|
|
import java.io.File
|
|
|
|
object NotificationSoundResolver {
|
|
private const val CUSTOM_SOUND_FILE = "custom_notification_sound"
|
|
|
|
fun resolve(context: Context, settings: NotificationSoundSettings): Uri {
|
|
return when (settings.source) {
|
|
NotificationSoundSource.SYSTEM -> defaultNotificationUri()
|
|
NotificationSoundSource.CUSTOM -> {
|
|
playableCustomUri(context, settings.customUri) ?: defaultNotificationUri()
|
|
}
|
|
}
|
|
}
|
|
|
|
suspend fun normalizeStoredSettings(context: Context, store: SessionStore): NotificationSoundSettings {
|
|
val settings = store.getNotificationSoundSettings()
|
|
if (settings.source != NotificationSoundSource.CUSTOM || settings.customUri.isNullOrBlank()) {
|
|
return settings
|
|
}
|
|
if (isInternalSoundUri(context, settings.customUri)) {
|
|
return settings
|
|
}
|
|
val imported = importCustomSound(context, Uri.parse(settings.customUri)) ?: return settings
|
|
store.setNotificationSound(NotificationSoundSource.CUSTOM, imported)
|
|
return store.getNotificationSoundSettings()
|
|
}
|
|
|
|
fun defaultNotificationUri(): Uri =
|
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
|
|
fun displayName(context: Context, settings: NotificationSoundSettings): String {
|
|
return try {
|
|
val uri = resolve(context, settings)
|
|
RingtoneManager.getRingtone(context, uri)?.getTitle(context) ?: "—"
|
|
} catch (_: Exception) {
|
|
"—"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies picker/system URI into app storage so FCM in a fresh process can read it.
|
|
* Returns stored URI string (FileProvider or android.resource).
|
|
*/
|
|
fun importCustomSound(context: Context, source: Uri): String? {
|
|
if (ContentResolver.SCHEME_ANDROID_RESOURCE == source.scheme) {
|
|
return source.toString()
|
|
}
|
|
if (defaultNotificationUri().toString() == source.toString()) {
|
|
return source.toString()
|
|
}
|
|
persistCustomUri(context, source)
|
|
val dest = File(context.filesDir, CUSTOM_SOUND_FILE)
|
|
return try {
|
|
context.contentResolver.openInputStream(source)?.use { input ->
|
|
dest.outputStream().use { output -> input.copyTo(output) }
|
|
} ?: return null
|
|
internalFileUri(context, dest).toString()
|
|
} catch (_: Exception) {
|
|
null
|
|
}
|
|
}
|
|
|
|
private fun playableCustomUri(context: Context, stored: String?): Uri? {
|
|
if (stored.isNullOrBlank()) return null
|
|
val parsed = Uri.parse(stored)
|
|
if (isInternalSoundUri(context, stored)) {
|
|
val file = File(context.filesDir, CUSTOM_SOUND_FILE)
|
|
return if (file.isFile) internalFileUri(context, file) else null
|
|
}
|
|
if (ContentResolver.SCHEME_ANDROID_RESOURCE == parsed.scheme) {
|
|
return parsed
|
|
}
|
|
return importCustomSound(context, parsed)?.let { Uri.parse(it) }
|
|
}
|
|
|
|
private fun isInternalSoundUri(context: Context, stored: String): Boolean {
|
|
val parsed = Uri.parse(stored)
|
|
return parsed.authority == fileProviderAuthority(context)
|
|
}
|
|
|
|
private fun internalFileUri(context: Context, file: File): Uri =
|
|
FileProvider.getUriForFile(context, fileProviderAuthority(context), file)
|
|
|
|
private fun fileProviderAuthority(context: Context): String =
|
|
"${context.packageName}.fileprovider"
|
|
|
|
private fun persistCustomUri(context: Context, uri: Uri) {
|
|
if (uri.scheme != ContentResolver.SCHEME_CONTENT) return
|
|
try {
|
|
context.contentResolver.takePersistableUriPermission(
|
|
uri,
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
|
)
|
|
} catch (_: SecurityException) {
|
|
// Best-effort; internal copy is the reliable path.
|
|
}
|
|
}
|
|
}
|