Add device-specific support configs.

This commit is contained in:
Michelle Tang
2024-06-21 10:45:22 -07:00
committed by Greyson Parrelli
parent c0da0bd272
commit b806952430
10 changed files with 203 additions and 15 deletions

View File

@@ -10,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import org.signal.core.util.concurrent.LifecycleDisposable
@@ -17,6 +18,7 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.PromptBatterySaverBottomSheetBinding
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.DelayedNotificationConfig
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.LocalMetrics
import org.thoughtcrime.securesms.util.PowerManagerCompat
@@ -26,12 +28,15 @@ class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFrag
companion object {
private val TAG = Log.tag(PromptBatterySaverDialogFragment::class.java)
private const val ARG_LEARN_MORE_LINK = "arg.learn.more.link"
@JvmStatic
fun show(fragmentManager: FragmentManager) {
if (fragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
PromptBatterySaverDialogFragment().apply {
arguments = bundleOf()
arguments = bundleOf(
ARG_LEARN_MORE_LINK to DelayedNotificationConfig.currentConfig.link
)
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
SignalStore.uiHints.lastBatterySaverPrompt = System.currentTimeMillis()
}
@@ -52,6 +57,11 @@ class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFrag
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
val learnMoreLink = arguments?.getString(ARG_LEARN_MORE_LINK) ?: getString(R.string.PromptBatterySaverBottomSheet__learn_more_url)
binding.message.setLearnMoreVisible(true)
binding.message.setLinkColor(ContextCompat.getColor(requireContext(), R.color.signal_colorPrimary))
binding.message.setLink(learnMoreLink)
binding.continueButton.setOnClickListener {
PowerManagerCompat.requestIgnoreBatteryOptimizations(requireContext())
Log.i(TAG, "Requested to ignore battery optimizations, clearing local metrics.")

View File

@@ -120,7 +120,7 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
messagePrivacy = SignalStore.settings.messageNotificationsPrivacy.toString(),
priority = TextSecurePreferences.getNotificationPriority(AppDependencies.application),
troubleshootNotifications = if (calculateSlowNotifications) {
SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && SlowNotificationHeuristics.isHavingDelayedNotifications()
SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && (SlowNotificationHeuristics.isHavingDelayedNotifications() || SlowNotificationHeuristics.showPreemptively())
} else if (currentState != null) {
currentState.messageNotificationsState.troubleshootNotifications
} else {

View File

@@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.notifications
import android.os.Build
import androidx.annotation.VisibleForTesting
import com.fasterxml.jackson.annotation.JsonProperty
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.RemoteConfig
import java.io.IOException
/**
* Remote configs for a device to show a support screen in an effort to prevent delayed notifications
*/
object DelayedNotificationConfig {
private val TAG = Log.tag(DelayedNotificationConfig::class.java)
private const val GENERAL_SUPPORT_URL = "https://support.signal.org/hc/articles/360007318711#android_notifications_troubleshooting"
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 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
*/
data class Config(
@JsonProperty val model: String = "",
@JsonProperty val showPreemptively: Boolean = false,
@JsonProperty val link: String = GENERAL_SUPPORT_URL,
@JsonProperty val localePercent: String = RemoteConfig.promptBatterySaver
)
@VisibleForTesting
fun computeConfig(): Config {
val default = Config()
val serialized = RemoteConfig.promptDelayedNotificationConfig
if (serialized.isNullOrBlank()) {
return default
}
val list: List<Config> = try {
JsonUtils.fromJsonArray(serialized, Config::class.java)
} catch (e: IOException) {
Log.w(TAG, "Failed to parse json!", e)
emptyList()
}
val config: Config? = list
.filter { it.model.isNotEmpty() }
.find {
if (it.model.last() == '*') {
Build.MODEL.startsWith(it.model.substring(0, it.model.length - 1))
} else {
it.model.contains(Build.MODEL)
}
}
return config ?: default
}
}

View File

@@ -143,6 +143,10 @@ object SlowNotificationHeuristics {
return true
}
fun showPreemptively(): Boolean {
return DelayedNotificationConfig.currentConfig.showPreemptively
}
private fun hasRepeatedFailedServiceStarts(metrics: List<LocalMetricsDatabase.EventMetrics>, minimumEventAgeMs: Long, minimumEventCount: Int, failurePercentage: Float): Boolean {
if (!haveEnoughData(SignalLocalMetrics.FcmServiceStartSuccess.NAME, minimumEventAgeMs) && !haveEnoughData(SignalLocalMetrics.FcmServiceStartFailure.NAME, minimumEventAgeMs)) {
Log.d(TAG, "insufficient data for service starts")

View File

@@ -46,7 +46,7 @@ class VitalsViewModel(private val context: Application) : AndroidViewModel(conte
private fun checkHeuristics(): Single<State> {
return Single.fromCallable {
var state = State.NONE
if (SlowNotificationHeuristics.isHavingDelayedNotifications()) {
if (SlowNotificationHeuristics.showPreemptively() || SlowNotificationHeuristics.isHavingDelayedNotifications()) {
if (SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && SlowNotificationHeuristics.shouldPromptBatterySaver()) {
state = State.PROMPT_BATTERY_SAVER_DIALOG
} else if (SlowNotificationHeuristics.shouldPromptUserForLogs()) {

View File

@@ -8,6 +8,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
import org.thoughtcrime.securesms.notifications.DelayedNotificationConfig;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Arrays;
@@ -74,7 +75,7 @@ public final class LocaleRemoteConfig {
}
public static boolean isBatterySaverPromptEnabled() {
return RemoteConfig.internalUser() || isEnabledPartsPerMillion(RemoteConfig.PROMPT_BATTERY_SAVER, RemoteConfig.promptBatterySaver());
return RemoteConfig.internalUser() || isEnabledPartsPerMillion(RemoteConfig.PROMPT_BATTERY_SAVER, DelayedNotificationConfig.INSTANCE.getCurrentConfig().getLocalePercent());
}
/**

View File

@@ -16,6 +16,9 @@ import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
import org.thoughtcrime.securesms.jobs.Svr3MirrorJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver
import org.thoughtcrime.securesms.util.RemoteConfig.Config
import org.thoughtcrime.securesms.util.RemoteConfig.remoteBoolean
import org.thoughtcrime.securesms.util.RemoteConfig.remoteValue
import java.io.IOException
import java.util.TreeMap
import kotlin.math.max
@@ -873,6 +876,14 @@ object RemoteConfig {
hotSwappable = true
)
private const val PROMPT_DELAYED_NOTIFICATION_CONFIG: String = "android.delayedNotificationConfig"
val promptDelayedNotificationConfig: String by remoteString(
key = PROMPT_DELAYED_NOTIFICATION_CONFIG,
defaultValue = "",
hotSwappable = true
)
const val CRASH_PROMPT_CONFIG: String = "android.crashPromptConfig"
/** Config object for what crashes to prompt about. */