Update device-specific notification support configs.

This commit is contained in:
Michelle Tang
2024-07-09 12:04:09 -04:00
committed by Cody Henthorne
parent 60a0565ba8
commit 9024c19169
14 changed files with 319 additions and 131 deletions

View File

@@ -11,32 +11,50 @@ import java.io.IOException
/**
* Remote configs for a device to show a support screen in an effort to prevent delayed notifications
*/
object DelayedNotificationConfig {
object DeviceSpecificNotificationConfig {
private val TAG = Log.tag(DelayedNotificationConfig::class.java)
private val TAG = Log.tag(DeviceSpecificNotificationConfig::class.java)
private const val GENERAL_SUPPORT_URL = "https://support.signal.org/hc/articles/360007318711#android_notifications_troubleshooting"
@JvmStatic
val currentConfig: Config by lazy { computeConfig() }
/**
* Maps a device model to specific modifications set in order to support better notification
* @param model either exact device model name or model name that ends with a wildcard
* @param showPreemptively shows support sheet immediately if true or after a vitals failure if not, still dependent on localePercent
* @param showConditionCode outlines under which conditions to show the prompt, still dependent on localePercent
* @param link represents the Signal support url that corresponds to this device model
* @param localePercent represents the percent of people who will get this change per country
* @param version represents the version of the link being shown and should be incremented if the link or link content changes
*/
data class Config(
@JsonProperty val model: String = "",
@JsonProperty val showPreemptively: Boolean = false,
@JsonProperty val showConditionCode: String = "has-slow-notifications",
@JsonProperty val link: String = GENERAL_SUPPORT_URL,
@JsonProperty val localePercent: String = RemoteConfig.promptBatterySaver
)
@JsonProperty val localePercent: String = "*",
@JsonProperty val version: Int = 0
) {
val showCondition: ShowCondition = ShowCondition.fromCode(showConditionCode)
}
/**
* Describes under which conditions to show device help prompt
*/
enum class ShowCondition(val code: String) {
ALWAYS("always"),
HAS_BATTERY_OPTIMIZATION_ON("has-battery-optimization-on"),
HAS_SLOW_NOTIFICATIONS("has-slow-notifications");
companion object {
fun fromCode(code: String) = values().firstOrNull { it.code == code } ?: HAS_SLOW_NOTIFICATIONS
}
}
@VisibleForTesting
fun computeConfig(): Config {
val default = Config()
val serialized = RemoteConfig.promptDelayedNotificationConfig
if (serialized.isNullOrBlank()) {
val serialized = RemoteConfig.deviceSpecificNotificationConfig
if (serialized.isBlank()) {
return default
}

View File

@@ -132,7 +132,7 @@ object SlowNotificationHeuristics {
* true can most definitely be at fault.
*/
@JvmStatic
fun isPotentiallyCausedByBatteryOptimizations(): Boolean {
fun isBatteryOptimizationsOn(): Boolean {
val applicationContext = AppDependencies.application
if (DeviceProperties.getDataSaverState(applicationContext) == DeviceProperties.DataSaverState.ENABLED) {
return false
@@ -143,8 +143,12 @@ object SlowNotificationHeuristics {
return true
}
fun showPreemptively(): Boolean {
return DelayedNotificationConfig.currentConfig.showPreemptively
fun showCondition(): DeviceSpecificNotificationConfig.ShowCondition {
return DeviceSpecificNotificationConfig.currentConfig.showCondition
}
fun shouldShowDialog(): Boolean {
return LocaleRemoteConfig.isDeviceSpecificNotificationEnabled() && SignalStore.uiHints.lastSupportVersionSeen < DeviceSpecificNotificationConfig.currentConfig.version
}
private fun hasRepeatedFailedServiceStarts(metrics: List<LocalMetricsDatabase.EventMetrics>, minimumEventAgeMs: Long, minimumEventCount: Int, failurePercentage: Float): Boolean {

View File

@@ -46,17 +46,29 @@ class VitalsViewModel(private val context: Application) : AndroidViewModel(conte
private fun checkHeuristics(): Single<State> {
return Single.fromCallable {
var state = State.NONE
if (SlowNotificationHeuristics.showPreemptively() || SlowNotificationHeuristics.isHavingDelayedNotifications()) {
if (SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && SlowNotificationHeuristics.shouldPromptBatterySaver()) {
state = State.PROMPT_BATTERY_SAVER_DIALOG
} else if (SlowNotificationHeuristics.shouldPromptUserForLogs()) {
state = State.PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS
when (SlowNotificationHeuristics.showCondition()) {
DeviceSpecificNotificationConfig.ShowCondition.ALWAYS -> {
if (SlowNotificationHeuristics.shouldShowDialog()) {
state = State.PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG
}
}
} else if (LogDatabase.getInstance(context).crashes.anyMatch(patterns = CrashConfig.patterns, promptThreshold = System.currentTimeMillis() - 14.days.inWholeMilliseconds)) {
val timeSinceLastPrompt = System.currentTimeMillis() - SignalStore.uiHints.lastCrashPrompt
DeviceSpecificNotificationConfig.ShowCondition.HAS_BATTERY_OPTIMIZATION_ON -> {
if (SlowNotificationHeuristics.isBatteryOptimizationsOn()) {
state = State.PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG
}
}
DeviceSpecificNotificationConfig.ShowCondition.HAS_SLOW_NOTIFICATIONS -> {
if (SlowNotificationHeuristics.isHavingDelayedNotifications() && SlowNotificationHeuristics.shouldPromptBatterySaver()) {
state = State.PROMPT_GENERAL_BATTERY_SAVER_DIALOG
} else if (SlowNotificationHeuristics.isHavingDelayedNotifications() && SlowNotificationHeuristics.shouldPromptUserForLogs()) {
state = State.PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS
} else if (LogDatabase.getInstance(context).crashes.anyMatch(patterns = CrashConfig.patterns, promptThreshold = System.currentTimeMillis() - 14.days.inWholeMilliseconds)) {
val timeSinceLastPrompt = System.currentTimeMillis() - SignalStore.uiHints.lastCrashPrompt
if (timeSinceLastPrompt > 1.days.inWholeMilliseconds) {
state = State.PROMPT_DEBUGLOGS_FOR_CRASH
if (timeSinceLastPrompt > 1.days.inWholeMilliseconds) {
state = State.PROMPT_DEBUGLOGS_FOR_CRASH
}
}
}
}
@@ -66,7 +78,8 @@ class VitalsViewModel(private val context: Application) : AndroidViewModel(conte
enum class State {
NONE,
PROMPT_BATTERY_SAVER_DIALOG,
PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG,
PROMPT_GENERAL_BATTERY_SAVER_DIALOG,
PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS,
PROMPT_DEBUGLOGS_FOR_CRASH
}