Add prompt to help troubleshoot slow notifications.

This commit is contained in:
Clark 2023-08-15 12:31:54 -04:00 committed by Cody Henthorne
parent 98ec2cceb4
commit 4cbcee85d6
16 changed files with 329 additions and 10 deletions

View file

@ -90,6 +90,8 @@ import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.PowerManagerCompat;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
import org.thoughtcrime.securesms.util.TextSecurePreferences;

View file

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
@ -16,12 +17,14 @@ import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment;
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSheetFragment;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
@ -30,6 +33,7 @@ import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.PowerManagerCompat;
import org.thoughtcrime.securesms.util.SplashScreenUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
@ -139,8 +143,14 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
updateTabVisibility();
if (SlowNotificationHeuristics.shouldPromptUserForLogs()) {
DebugLogsPromptDialogFragment.show(this, getSupportFragmentManager());
if (SlowNotificationHeuristics.isHavingDelayedNotifications()) {
if (SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && Build.VERSION.SDK_INT >= 23) {
if (SlowNotificationHeuristics.shouldPromptBatterySaver()) {
PromptBatterySaverDialogFragment.show(this, getSupportFragmentManager());
}
} else if (SlowNotificationHeuristics.shouldPromptUserForLogs()) {
DebugLogsPromptDialogFragment.show(this, getSupportFragmentManager());
}
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import org.signal.core.util.concurrent.LifecycleDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.PromptBatterySaverBottomSheetBinding
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.PowerManagerCompat
@RequiresApi(23)
class PromptBatterySaverDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
companion object {
@JvmStatic
fun show(context: Context, fragmentManager: FragmentManager) {
if (fragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
PromptBatterySaverDialogFragment().apply {
arguments = bundleOf()
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
SignalStore.uiHints().lastBatterySaverPrompt = System.currentTimeMillis()
}
}
}
override val peekHeightPercentage: Float = 0.66f
override val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners_Messages
private val binding by ViewBinderDelegate(PromptBatterySaverBottomSheetBinding::bind)
private lateinit var viewModel: PromptLogsViewModel
private val disposables: LifecycleDisposable = LifecycleDisposable()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.prompt_battery_saver_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
viewModel = ViewModelProvider(this)[PromptLogsViewModel::class.java]
binding.continueButton.setOnClickListener {
PowerManagerCompat.requestIgnoreBatteryOptimizations(requireContext())
}
binding.dismissButton.setOnClickListener {
SignalStore.uiHints().markDismissedBatterySaverPrompt()
dismiss()
}
}
}

View file

@ -22,6 +22,7 @@ import androidx.preference.PreferenceManager
import org.signal.core.util.getParcelableExtraCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
@ -184,6 +185,16 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
}
)
if (Build.VERSION.SDK_INT >= 23 && state.messageNotificationsState.troubleshootNotifications) {
clickPref(
title = DSLSettingsText.from(R.string.preferences_notifications__troubleshoot),
isEnabled = true,
onClick = {
PromptBatterySaverDialogFragment.show(requireContext(), childFragmentManager)
}
)
}
if (Build.VERSION.SDK_INT < 30) {
if (NotificationChannels.supported()) {
clickPref(

View file

@ -17,7 +17,8 @@ data class MessageNotificationsState(
val inChatSoundsEnabled: Boolean,
val repeatAlerts: Int,
val messagePrivacy: String,
val priority: Int
val priority: Int,
val troubleshootNotifications: Boolean
)
data class CallNotificationsState(

View file

@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
@ -104,7 +105,8 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
inChatSoundsEnabled = SignalStore.settings().isMessageNotificationsInChatSoundsEnabled,
repeatAlerts = SignalStore.settings().messageNotificationsRepeatAlerts,
messagePrivacy = SignalStore.settings().messageNotificationsPrivacy.toString(),
priority = TextSecurePreferences.getNotificationPriority(ApplicationDependencies.getApplication())
priority = TextSecurePreferences.getNotificationPriority(ApplicationDependencies.getApplication()),
troubleshootNotifications = SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && SlowNotificationHeuristics.isHavingDelayedNotifications()
),
callNotificationsState = CallNotificationsState(
notificationsEnabled = SignalStore.settings().isCallNotificationsEnabled,

View file

@ -20,6 +20,8 @@ public class UiHints extends SignalStoreValues {
private static final String HAS_SEEN_SAFETY_NUMBER_NUX = "uihints.has_seen_safety_number_nux";
private static final String DECLINED_NOTIFICATION_LOGS_PROMPT = "uihints.declined_notification_logs";
private static final String LAST_NOTIFICATION_LOGS_PROMPT_TIME = "uihints.last_notification_logs_prompt";
private static final String DISMISSED_BATTERY_SAVER_PROMPT = "uihints.declined_battery_saver_prompt";
private static final String LAST_BATTERY_SAVER_PROMPT = "uihints.last_battery_saver_prompt";
UiHints(@NonNull KeyValueStore store) {
super(store);
@ -136,4 +138,20 @@ public class UiHints extends SignalStoreValues {
public boolean hasDeclinedToShareNotificationLogs() {
return getBoolean(DECLINED_NOTIFICATION_LOGS_PROMPT, false);
}
public void markDismissedBatterySaverPrompt() {
putBoolean(DISMISSED_BATTERY_SAVER_PROMPT, true);
}
public boolean hasDismissedBatterySaverPrompt() {
return getBoolean(DISMISSED_BATTERY_SAVER_PROMPT, false);
}
public long getLastBatterySaverPrompt() {
return getLong(LAST_BATTERY_SAVER_PROMPT, 0);
}
public void setLastBatterySaverPrompt(long time) {
putLong(LAST_BATTERY_SAVER_PROMPT, time);
}
}

View file

@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.emoji.EmojiFiles;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.webrtc.AndroidTelecomUtil;
import org.thoughtcrime.securesms.util.AppSignatureUtil;
@ -88,7 +89,10 @@ public class LogSectionSystemInfo implements LogSection {
builder.append("Server Time Offset: ").append(SignalStore.misc().getLastKnownServerTimeOffset()).append(" ms (last updated: ").append(SignalStore.misc().getLastKnownServerTimeOffsetUpdateTime()).append(")").append("\n");
builder.append("Telecom : ").append(AndroidTelecomUtil.getTelecomSupported()).append("\n");
builder.append("User-Agent : ").append(StandardUserAgentInterceptor.USER_AGENT).append("\n");
builder.append("SlowNotifications : ").append(SlowNotificationHeuristics.isHavingDelayedNotifications()).append("\n");
builder.append("PotentiallyBattery: ").append(SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations()).append("\n");
builder.append("App : ");
try {
builder.append(pm.getApplicationLabel(pm.getApplicationInfo(context.getPackageName(), 0)))
.append(" ")

View file

@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDona
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;

View file

@ -5,14 +5,17 @@
package org.thoughtcrime.securesms.notifications
import android.os.Build
import android.text.TextUtils
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.DeviceProperties
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.LocaleFeatureFlags
import org.thoughtcrime.securesms.util.PowerManagerCompat
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.days
@ -63,12 +66,34 @@ object SlowNotificationHeuristics {
if (System.currentTimeMillis() - SignalStore.uiHints().lastNotificationLogsPrompt < TimeUnit.DAYS.toMillis(7)) {
return false
}
val configuration = getConfiguration()
return isHavingDelayedNotifications(configuration)
return true
}
fun isHavingDelayedNotifications(configuration: Configuration): Boolean {
@JvmStatic
fun shouldPromptBatterySaver(): Boolean {
if (Build.VERSION.SDK_INT < 23) {
return false
}
if (!LocaleFeatureFlags.isBatterySaverPromptEnabled() || SignalStore.uiHints().hasDismissedBatterySaverPrompt()) {
return false
}
if (System.currentTimeMillis() - SignalStore.uiHints().lastBatterySaverPrompt < TimeUnit.DAYS.toMillis(7)) {
return false
}
return true
}
@JvmStatic
fun isHavingDelayedNotifications(): Boolean {
if (!SignalStore.settings().isMessageNotificationsEnabled ||
!NotificationChannels.getInstance().areNotificationsEnabled()
) {
// If user does not have notifications enabled, we shouldn't bother them about delayed notifications
return false
}
val configuration = getConfiguration()
val db = LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication())
val metrics = db.getMetrics()
@ -84,6 +109,32 @@ object SlowNotificationHeuristics {
return false
}
/**
* Returns whether or not the delayed notifications may be due to battery saver optimizations.
*
* Some OEMs over-optimize this battery restrictions and remove network even after receiving a
* high priority push.
*
* We consider a scenario where removing battery optimizations can fix delayed notifications:
* - Data saver must be off (or white listed), otherwise it can be causing delayed notifications
* - App must not already be exempted from battery optimizations
*
* We do not need to check if {ActivityManager#isBackgroundRestricted} is true, because if the app
* is set to "Optimized" this will be false (and can be culprit to delayed notifications) or if
* true can most definitely be at fault.
*/
@JvmStatic
fun isPotentiallyCausedByBatteryOptimizations(): Boolean {
val applicationContext = ApplicationDependencies.getApplication()
if (DeviceProperties.getDataSaverState(applicationContext) == DeviceProperties.DataSaverState.ENABLED) {
return false
}
if (PowerManagerCompat.isIgnoringBatteryOptimizations(applicationContext)) {
return false
}
return true
}
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

@ -111,6 +111,7 @@ public final class FeatureFlags {
private static final String SAFETY_NUMBER_ACI = "global.safetyNumberAci";
public static final String PROMPT_FOR_NOTIFICATION_LOGS = "android.logs.promptNotifications";
private static final String PROMPT_FOR_NOTIFICATION_CONFIG = "android.logs.promptNotificationsConfig";
public static final String PROMPT_BATTERY_SAVER = "android.promptBatterySaver";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -173,7 +174,8 @@ public final class FeatureFlags {
SAFETY_NUMBER_ACI,
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH,
PROMPT_FOR_NOTIFICATION_LOGS,
PROMPT_FOR_NOTIFICATION_CONFIG
PROMPT_FOR_NOTIFICATION_CONFIG,
PROMPT_BATTERY_SAVER
);
@VisibleForTesting
@ -242,7 +244,8 @@ public final class FeatureFlags {
SAFETY_NUMBER_ACI,
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH,
PROMPT_FOR_NOTIFICATION_LOGS,
PROMPT_FOR_NOTIFICATION_CONFIG
PROMPT_FOR_NOTIFICATION_CONFIG,
PROMPT_BATTERY_SAVER
);
/**
@ -631,6 +634,11 @@ public final class FeatureFlags {
public static String delayedNotificationsPromptConfig() {
return getString(PROMPT_FOR_NOTIFICATION_CONFIG, "");
}
public static String promptBatterySaver() {
return getString(PROMPT_BATTERY_SAVER, "*");
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View file

@ -68,6 +68,11 @@ public final class LocaleFeatureFlags {
public static boolean isDelayedNotificationPromptEnabled() {
return isEnabled(FeatureFlags.PROMPT_FOR_NOTIFICATION_LOGS, FeatureFlags.promptForDelayedNotificationLogs());
}
public static boolean isBatterySaverPromptEnabled() {
return isEnabled(FeatureFlags.PROMPT_BATTERY_SAVER, FeatureFlags.PROMPT_BATTERY_SAVER);
}
/**
* Parses a comma-separated list of country codes colon-separated from how many buckets out of 1 million
* should be enabled to see this megaphone in that country code. At the end of the list, an optional

View file

@ -19,6 +19,13 @@ public class PowerManagerCompat {
return false;
}
public static boolean isIgnoringBatteryOptimizations(@NonNull Context context) {
if (Build.VERSION.SDK_INT < 23) {
return true;
}
return ServiceUtil.getPowerManager(context).isIgnoringBatteryOptimizations(context.getPackageName());
}
@RequiresApi(api = 23)
public static void requestIgnoreBatteryOptimizations(@NonNull Context context) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,

View file

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="72"
android:viewportHeight="72">
<path
android:pathData="M24.75,56.25C24.75,62.625 29.625,67.5 36,67.5C42.375,67.5 47.25,62.625 47.25,56.25"
android:fillColor="#C0CBE2"/>
<path
android:pathData="M5.625,51.242C5.625,54.008 7.83,56.25 10.551,56.25H61.449C64.17,56.25 66.375,54.008 66.375,51.242C66.375,44.247 57.372,48.738 56.524,24.532C56.113,12.824 46.882,4.5 36,4.5C25.118,4.5 15.887,12.824 15.476,24.532C14.628,48.738 5.625,44.247 5.625,51.242Z"
android:fillColor="#DDE7FF"/>
<path
android:pathData="M10.551,56.25C7.83,56.25 5.625,54.008 5.625,51.242C5.625,48.77 6.749,47.732 8.241,46.356C10.643,44.139 13.998,41.042 15.141,29.656C21.026,25.465 28.225,23 36,23C43.775,23 50.974,25.465 56.859,29.656C58.002,41.042 61.357,44.139 63.759,46.356C65.251,47.732 66.375,48.77 66.375,51.242C66.375,54.008 64.17,56.25 61.449,56.25H10.551Z"
android:fillColor="#C0CBE2"/>
<path
android:pathData="M36,69C28.797,69 23.25,63.453 23.25,56.25H26.25C26.25,61.797 30.453,66 36,66C41.547,66 45.75,61.797 45.75,56.25H48.75C48.75,63.453 43.203,69 36,69Z"
android:fillColor="#6C7B9D"
android:fillType="evenOdd"/>
<path
android:pathData="M36,6C25.908,6 17.357,13.703 16.975,24.585C16.545,36.869 14.035,42.179 11.376,45.312C10.729,46.074 10.095,46.682 9.539,47.198C9.436,47.294 9.337,47.386 9.242,47.473C8.808,47.874 8.461,48.195 8.154,48.532C7.51,49.239 7.125,49.92 7.125,51.242C7.125,53.203 8.682,54.75 10.551,54.75H61.449C63.318,54.75 64.875,53.203 64.875,51.242C64.875,49.92 64.49,49.239 63.846,48.532C63.539,48.195 63.192,47.874 62.758,47.473C62.663,47.386 62.564,47.294 62.461,47.198C61.905,46.682 61.271,46.074 60.624,45.312C57.965,42.179 55.455,36.869 55.025,24.585C54.643,13.703 46.092,6 36,6ZM13.977,24.48C14.417,11.946 24.329,3 36,3C47.671,3 57.583,11.946 58.023,24.48C58.44,36.402 60.856,40.949 62.911,43.37C63.442,43.996 63.973,44.509 64.503,45C64.587,45.079 64.674,45.159 64.761,45.239C65.198,45.642 65.661,46.069 66.064,46.511C67.134,47.686 67.875,49.066 67.875,51.242C67.875,54.813 65.021,57.75 61.449,57.75H10.551C6.979,57.75 4.125,54.813 4.125,51.242C4.125,49.066 4.866,47.686 5.936,46.511C6.339,46.069 6.802,45.642 7.239,45.239C7.326,45.159 7.413,45.079 7.497,45C8.027,44.509 8.558,43.996 9.089,43.37C11.144,40.949 13.559,36.402 13.977,24.48Z"
android:fillColor="#6C7B9D"
android:fillType="evenOdd"/>
<path
android:pathData="M53,18m-15,0a15,15 0,1 1,30 0a15,15 0,1 1,-30 0"
android:fillColor="#FA902E"/>
<path
android:pathData="M53,4.5C45.544,4.5 39.5,10.544 39.5,18C39.5,25.456 45.544,31.5 53,31.5C60.456,31.5 66.5,25.456 66.5,18C66.5,10.544 60.456,4.5 53,4.5ZM36.5,18C36.5,8.887 43.887,1.5 53,1.5C62.113,1.5 69.5,8.887 69.5,18C69.5,27.113 62.113,34.5 53,34.5C43.887,34.5 36.5,27.113 36.5,18Z"
android:fillColor="#CE711B"
android:fillType="evenOdd"/>
<path
android:pathData="M54.972,11.695C55.062,10.545 54.153,9.563 53,9.563C51.847,9.563 50.938,10.545 51.028,11.695L51.661,19.762C51.716,20.461 52.299,21 53,21C53.701,21 54.284,20.461 54.339,19.762L54.972,11.695Z"
android:fillColor="#FFE3A5"/>
<path
android:pathData="M53,22.75C51.861,22.75 50.938,23.673 50.938,24.813C50.938,25.952 51.861,26.875 53,26.875C54.139,26.875 55.063,25.952 55.063,24.813C55.063,23.673 54.139,22.75 53,22.75Z"
android:fillColor="#FFE3A5"/>
</vector>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="48dp"
android:layout_height="2dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:background="@color/signal_icon_tint_tab_unselected"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_troubleshoot_notification"
android:layout_marginTop="32dp"
android:layout_gravity="center_horizontal"/>
<TextView
style="@style/Signal.Text.TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:layout_marginTop="16dp"
android:text="@string/PromptBatterySaverBottomSheet__title"
/>
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/message"
style="@style/Signal.Text.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:layout_marginTop="12dp"
android:layout_marginHorizontal="24dp"
android:text="@string/PromptBatterySaverBottomSheet__message"
/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:minWidth="320dp"
android:gravity="center_horizontal"
android:layout_marginHorizontal="34dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/dismiss_button"
style="@style/Signal.Widget.Button.Medium.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:minWidth="160dp"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:text="@string/PromptBatterySaverBottomSheet__dismiss"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/continue_button"
style="@style/Signal.Widget.Button.Medium.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:minWidth="160dp"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:text="@string/PromptBatterySaverBottomSheet__continue"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -910,6 +910,16 @@
<!-- Message for dialog asking user to submit logs for debugging slow notification issues -->
<string name="PromptLogsSlowNotificationsDialog__message">Debug logs helps us diagnose and fix the issue, and do not contain identifying information.</string>
<!-- Title for dialog asking user to submit logs for debugging slow notification issues -->
<string name="PromptBatterySaverBottomSheet__title">Notifications may be delayed due to battery optimizations</string>
<!-- Message explaining that battery saver may delay notifications -->
<string name="PromptBatterySaverBottomSheet__message">You can disable battery optimizations for Signal to ensure that message notifications will not be delayed.</string>
<!-- Button to continue to try and disable battery saver -->
<string name="PromptBatterySaverBottomSheet__continue">Continue</string>
<!-- Button to dismiss battery saver dialog prompt-->
<string name="PromptBatterySaverBottomSheet__dismiss">Dismiss</string>
<!-- PendingMembersActivity -->
<string name="PendingMembersActivity_pending_group_invites">Pending group invites</string>
<string name="PendingMembersActivity_requests">Requests</string>
@ -3105,6 +3115,8 @@
<string name="preferences_notifications__ringtone">Ringtone</string>
<string name="preferences_chats__message_text_size">Message font size</string>
<string name="preferences_notifications__priority">Priority</string>
<!-- Option in settings to trouble shoot delayed notifications -->
<string name="preferences_notifications__troubleshoot">Troubleshoot notifications</string>
<!-- Heading for the \'censorship circumvention\' section of privacy preferences -->
<string name="preferences_communication__category_censorship_circumvention">Censorship circumvention</string>
<!-- Title of the \'censorship circumvention\' toggle switch -->