Merge pull request #5955 from k9mail/notification_settings_export

Read notification settings from `NotificationChannel` on settings export
This commit is contained in:
cketti 2022-03-10 21:51:09 +01:00 committed by GitHub
commit b9efb70d0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 130 additions and 28 deletions

View file

@ -114,4 +114,13 @@ val coreNotificationModule = module {
)
}
factory { NotificationLightDecoder() }
factory { NotificationVibrationDecoder() }
factory {
NotificationConfigurationConverter(notificationLightDecoder = get(), notificationVibrationDecoder = get())
}
factory {
NotificationSettingsUpdater(
preferences = get(), notificationChannelManager = get(), notificationConfigurationConverter = get()
)
}
}

View file

@ -0,0 +1,32 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.NotificationSettings
/**
* Converts the [NotificationConfiguration] read from a `NotificationChannel` into a [NotificationSettings] instance.
*/
class NotificationConfigurationConverter(
private val notificationLightDecoder: NotificationLightDecoder,
private val notificationVibrationDecoder: NotificationVibrationDecoder
) {
fun convert(account: Account, notificationConfiguration: NotificationConfiguration): NotificationSettings {
val light = notificationLightDecoder.decode(
isBlinkLightsEnabled = notificationConfiguration.isBlinkLightsEnabled,
lightColor = notificationConfiguration.lightColor,
accountColor = account.chipColor
)
val vibration = notificationVibrationDecoder.decode(
isVibrationEnabled = notificationConfiguration.isVibrationEnabled,
systemPattern = notificationConfiguration.vibrationPattern
)
return NotificationSettings(
isRingEnabled = notificationConfiguration.sound != null,
ringtone = notificationConfiguration.sound?.toString(),
light = light,
vibration = vibration
)
}
}

View file

@ -0,0 +1,34 @@
package com.fsck.k9.notification
import android.os.Build
import androidx.annotation.RequiresApi
import com.fsck.k9.Account
import com.fsck.k9.Preferences
/**
* Update accounts with notification settings read from their "Messages" `NotificationChannel`.
*/
class NotificationSettingsUpdater(
private val preferences: Preferences,
private val notificationChannelManager: NotificationChannelManager,
private val notificationConfigurationConverter: NotificationConfigurationConverter
) {
fun updateNotificationSettings(accountUuids: Collection<String>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
accountUuids
.mapNotNull { accountUuid -> preferences.getAccount(accountUuid) }
.forEach { account -> updateNotificationSettings(account) }
}
@RequiresApi(Build.VERSION_CODES.O)
private fun updateNotificationSettings(account: Account) {
val notificationConfiguration = notificationChannelManager.getNotificationConfiguration(account)
val notificationSettings = notificationConfigurationConverter.convert(account, notificationConfiguration)
if (notificationSettings != account.notificationSettings) {
account.updateNotificationSettings { notificationSettings }
preferences.saveAccount(account)
}
}
}

View file

@ -0,0 +1,26 @@
package com.fsck.k9.notification
import com.fsck.k9.NotificationVibration
import com.fsck.k9.VibratePattern
/**
* Converts the vibration values read from a `NotificationChannel` into [NotificationVibration].
*/
class NotificationVibrationDecoder {
fun decode(isVibrationEnabled: Boolean, systemPattern: List<Long>?): NotificationVibration {
if (systemPattern == null || systemPattern.size < 2 || systemPattern.size % 2 != 0) {
return NotificationVibration.DEFAULT
}
val systemPatternArray = systemPattern.toLongArray()
val repeatCount = systemPattern.size / 2
val pattern = VibratePattern.values()
.firstOrNull { vibratePattern ->
val testPattern = NotificationVibration.getSystemPattern(vibratePattern, repeatCount)
testPattern.contentEquals(systemPatternArray)
} ?: VibratePattern.Default
return NotificationVibration(isVibrationEnabled, pattern, repeatCount)
}
}

View file

@ -11,7 +11,8 @@ val preferencesModule = module {
contentResolver = get(),
preferences = get(),
folderSettingsProvider = get(),
folderRepository = get()
folderRepository = get(),
notificationSettingsUpdater = get()
)
}
factory { FolderSettingsProvider(folderRepository = get()) }

View file

@ -10,6 +10,7 @@ import com.fsck.k9.AccountPreferenceSerializer.Companion.IDENTITY_EMAIL_KEY
import com.fsck.k9.AccountPreferenceSerializer.Companion.IDENTITY_NAME_KEY
import com.fsck.k9.Preferences
import com.fsck.k9.mailstore.FolderRepository
import com.fsck.k9.notification.NotificationSettingsUpdater
import com.fsck.k9.preferences.ServerTypeConverter.fromServerSettingsType
import com.fsck.k9.preferences.Settings.InvalidSettingValueException
import com.fsck.k9.preferences.Settings.SettingsDescription
@ -24,10 +25,13 @@ class SettingsExporter(
private val contentResolver: ContentResolver,
private val preferences: Preferences,
private val folderSettingsProvider: FolderSettingsProvider,
private val folderRepository: FolderRepository
private val folderRepository: FolderRepository,
private val notificationSettingsUpdater: NotificationSettingsUpdater
) {
@Throws(SettingsImportExportException::class)
fun exportToUri(includeGlobals: Boolean, accountUuids: Set<String>, uri: Uri) {
updateNotificationSettings(accountUuids)
try {
contentResolver.openOutputStream(uri)!!.use { outputStream ->
exportPreferences(outputStream, includeGlobals, accountUuids)
@ -37,6 +41,16 @@ class SettingsExporter(
}
}
private fun updateNotificationSettings(accountUuids: Set<String>) {
try {
notificationSettingsUpdater.updateNotificationSettings(accountUuids)
} catch (e: Exception) {
// An error here could mean we export notification settings that don't reflect the current configuration
// of the notification channels. But we prefer stale data over failing the export.
Timber.w(e, "Error while updating accounts with notification configuration from system")
}
}
@Throws(SettingsImportExportException::class)
fun exportPreferences(outputStream: OutputStream, includeGlobals: Boolean, accountUuids: Set<String>) {
try {

View file

@ -11,6 +11,7 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.koin.core.component.inject
import org.mockito.kotlin.mock
import org.robolectric.RuntimeEnvironment
class SettingsExporterTest : K9RobolectricTest() {
@ -22,7 +23,8 @@ class SettingsExporterTest : K9RobolectricTest() {
contentResolver,
preferences,
folderSettingsProvider,
folderRepository
folderRepository,
notificationSettingsUpdater = mock()
)
@Test

View file

@ -29,6 +29,7 @@ import com.fsck.k9.mailstore.RemoteFolder
import com.fsck.k9.notification.NotificationChannelManager
import com.fsck.k9.notification.NotificationChannelManager.ChannelType
import com.fsck.k9.notification.NotificationLightDecoder
import com.fsck.k9.notification.NotificationVibrationDecoder
import com.fsck.k9.ui.R
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
import com.fsck.k9.ui.settings.onClick
@ -52,6 +53,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private val accountRemover: BackgroundAccountRemover by inject()
private val notificationChannelManager: NotificationChannelManager by inject()
private val notificationLightDecoder: NotificationLightDecoder by inject()
private val notificationVibrationDecoder: NotificationVibrationDecoder by inject()
private val vibrator by lazy { requireContext().getSystemService<Vibrator>() }
private lateinit var dataStore: AccountSettingsDataStore
@ -267,9 +269,14 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
}
notificationVibrationPreference?.let { preference ->
preference.setVibrationFromSystem(
val notificationVibration = notificationVibrationDecoder.decode(
isVibrationEnabled = notificationConfiguration.isVibrationEnabled,
combinedPattern = notificationConfiguration.vibrationPattern
systemPattern = notificationConfiguration.vibrationPattern
)
preference.setVibration(
isVibrationEnabled = notificationVibration.isEnabled,
vibratePattern = notificationVibration.pattern,
vibrationTimes = notificationVibration.repeatCount
)
preference.isEnabled = true
}

View file

@ -5,7 +5,6 @@ import android.content.Context
import android.util.AttributeSet
import androidx.core.content.res.TypedArrayUtils
import androidx.preference.ListPreference
import com.fsck.k9.NotificationVibration
import com.fsck.k9.VibratePattern
import com.fsck.k9.ui.R
import com.takisoft.preferencex.PreferenceFragmentCompat
@ -61,28 +60,6 @@ constructor(
updateSummary()
}
fun setVibrationFromSystem(isVibrationEnabled: Boolean, combinedPattern: List<Long>?) {
if (combinedPattern == null || combinedPattern.size < 2 || combinedPattern.size % 2 != 0) {
setVibration(isVibrationEnabled, DEFAULT_VIBRATE_PATTERN, DEFAULT_VIBRATION_TIMES)
return
}
val combinedPatternArray = combinedPattern.toLongArray()
val vibrationTimes = combinedPattern.size / 2
val vibrationPattern = entryValues.asSequence()
.map { entryValue ->
val serializedVibratePattern = entryValue.toString().toInt()
VibratePattern.deserialize(serializedVibratePattern)
}
.firstOrNull { vibratePattern ->
val testPattern = NotificationVibration.getSystemPattern(vibratePattern, vibrationTimes)
testPattern.contentEquals(combinedPatternArray)
} ?: DEFAULT_VIBRATE_PATTERN
setVibration(isVibrationEnabled, vibrationPattern, vibrationTimes)
}
private fun updateSummary() {
summary = if (isVibrationEnabled) {
val index = entryValues.indexOf(vibratePattern.serialize().toString())