Prompt user for debug logs with slow notifications.

This commit is contained in:
Clark
2023-08-09 12:18:25 -04:00
committed by Alex Hart
parent b51ec53e33
commit bb83ddfe28
11 changed files with 372 additions and 2 deletions

View File

@@ -15,12 +15,14 @@ import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment;
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.SlowNotificationHeuristics;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup;
@@ -136,6 +138,10 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
}
updateTabVisibility();
if (SlowNotificationHeuristics.shouldPromptUserForLogs()) {
DebugLogsPromptDialogFragment.show(this, getSupportFragmentManager());
}
}
@Override

View File

@@ -0,0 +1,103 @@
/*
* 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 android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import org.signal.core.util.ResourceUtil
import org.signal.core.util.concurrent.LifecycleDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.PromptLogsBottomSheetBinding
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.NetworkUtil
import org.thoughtcrime.securesms.util.SupportEmailUtil
class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
companion object {
@JvmStatic
fun show(context: Context, fragmentManager: FragmentManager) {
if (NetworkUtil.isConnected(context) && fragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
DebugLogsPromptDialogFragment().apply {
arguments = bundleOf()
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
SignalStore.uiHints().lastNotificationLogsPrompt = System.currentTimeMillis()
}
}
}
override val peekHeightPercentage: Float = 0.66f
override val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners_Messages
private val binding by ViewBinderDelegate(PromptLogsBottomSheetBinding::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_logs_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
viewModel = ViewModelProvider(this).get(PromptLogsViewModel::class.java)
binding.submit.setOnClickListener {
val progressDialog = SignalProgressDialog.show(requireContext())
disposables += viewModel.submitLogs().subscribe({ result ->
submitLogs(result)
progressDialog.dismiss()
dismiss()
}, { _ ->
Toast.makeText(requireContext(), getString(R.string.HelpFragment__could_not_upload_logs), Toast.LENGTH_LONG).show()
progressDialog.dismiss()
dismiss()
})
}
binding.decline.setOnClickListener {
SignalStore.uiHints().markDeclinedShareNotificationLogs()
dismiss()
}
}
private fun submitLogs(debugLog: String) {
CommunicationActions.openEmail(
requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getString(R.string.DebugLogsPromptDialogFragment__signal_android_support_request),
getEmailBody(debugLog)
)
}
private fun getEmailBody(debugLog: String?): String {
val suffix = StringBuilder()
if (debugLog != null) {
suffix.append("\n")
suffix.append(getString(R.string.HelpFragment__debug_log))
suffix.append(" ")
suffix.append(debugLog)
}
val category = ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__slow_notifications_category)
return SupportEmailUtil.generateSupportEmailBody(
requireContext(),
R.string.DebugLogsPromptDialogFragment__signal_android_support_request,
" - $category",
"\n\n",
suffix.toString()
)
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components
import androidx.lifecycle.ViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.SingleSubject
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository
class PromptLogsViewModel : ViewModel() {
private val submitDebugLogRepository = SubmitDebugLogRepository()
fun submitLogs(): Single<String> {
val singleSubject = SingleSubject.create<String?>()
submitDebugLogRepository.buildAndSubmitLog { result ->
if (result.isPresent) {
singleSubject.onSuccess(result.get())
} else {
singleSubject.onError(Throwable())
}
}
return singleSubject.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
}

View File

@@ -18,6 +18,8 @@ public class UiHints extends SignalStoreValues {
private static final String HAS_SEEN_TEXT_FORMATTING_ALERT = "uihints.text_formatting.has_seen_alert";
private static final String HAS_NOT_SEEN_EDIT_MESSAGE_BETA_ALERT = "uihints.edit_message.has_not_seen_beta_alert";
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";
UiHints(@NonNull KeyValueStore store) {
super(store);
@@ -118,4 +120,20 @@ public class UiHints extends SignalStoreValues {
public void markHasSeenSafetyNumberUpdateNux() {
putBoolean(HAS_SEEN_SAFETY_NUMBER_NUX, true);
}
public long getLastNotificationLogsPrompt() {
return getLong(LAST_NOTIFICATION_LOGS_PROMPT_TIME, 0);
}
public void setLastNotificationLogsPrompt(long timeMs) {
putLong(LAST_NOTIFICATION_LOGS_PROMPT_TIME, timeMs);
}
public void markDeclinedShareNotificationLogs() {
putBoolean(DECLINED_NOTIFICATION_LOGS_PROMPT, true);
}
public boolean hasDeclinedToShareNotificationLogs() {
return getBoolean(DECLINED_NOTIFICATION_LOGS_PROMPT, false);
}
}

View File

@@ -5,10 +5,18 @@
package org.thoughtcrime.securesms.notifications
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.FeatureFlags
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.LocaleFeatureFlags
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
/**
* Heuristic for estimating if a user has been experiencing issues with delayed notifications.
@@ -22,6 +30,44 @@ object SlowNotificationHeuristics {
private val TAG = Log.tag(SlowNotificationHeuristics::class.java)
fun getConfiguration(): Configuration {
val json = FeatureFlags.delayedNotificationsPromptConfig()
return if (TextUtils.isEmpty(json)) {
getDefaultConfiguration()
} else {
try {
JsonUtils.fromJson(json, Configuration::class.java)
} catch (exception: Exception) {
getDefaultConfiguration()
}
}
}
private fun getDefaultConfiguration(): Configuration {
return Configuration(
minimumEventAgeMs = 3.days.inWholeMilliseconds,
minimumServiceEventCount = 10,
serviceStartFailurePercentage = 0.5f,
messageLatencyPercentage = 75,
messageLatencyThreshold = 6.hours.inWholeMilliseconds,
minimumMessageLatencyEvents = 50,
weeklyFailedQueueDrains = 5
)
}
@JvmStatic
fun shouldPromptUserForLogs(): Boolean {
if (!LocaleFeatureFlags.isDelayedNotificationPromptEnabled() || SignalStore.uiHints().hasDeclinedToShareNotificationLogs()) {
return false
}
if (System.currentTimeMillis() - SignalStore.uiHints().lastNotificationLogsPrompt < TimeUnit.DAYS.toMillis(7)) {
return false
}
val configuration = getConfiguration()
return isHavingDelayedNotifications(configuration)
}
fun isHavingDelayedNotifications(configuration: Configuration): Boolean {
val db = LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication())

View File

@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
import org.thoughtcrime.securesms.notifications.Configuration;
import org.whispersystems.signalservice.api.RemoteConfigResult;
import java.io.IOException;
@@ -109,6 +110,8 @@ public final class FeatureFlags {
private static final String CDS_DISABLE_COMPAT_MODE = "cds.disableCompatibilityMode";
private static final String FCM_MAY_HAVE_MESSAGES_KILL_SWITCH = "android.fcmNotificationFallbackKillSwitch";
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";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@@ -169,7 +172,9 @@ public final class FeatureFlags {
SVR2_KILLSWITCH,
CDS_DISABLE_COMPAT_MODE,
SAFETY_NUMBER_ACI,
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH,
PROMPT_FOR_NOTIFICATION_LOGS,
PROMPT_FOR_NOTIFICATION_CONFIG
);
@VisibleForTesting
@@ -236,7 +241,9 @@ public final class FeatureFlags {
SVR2_KILLSWITCH,
CDS_DISABLE_COMPAT_MODE,
SAFETY_NUMBER_ACI,
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH
FCM_MAY_HAVE_MESSAGES_KILL_SWITCH,
PROMPT_FOR_NOTIFICATION_LOGS,
PROMPT_FOR_NOTIFICATION_CONFIG
);
/**
@@ -618,6 +625,13 @@ public final class FeatureFlags {
}
}
public static String promptForDelayedNotificationLogs() {
return getString(PROMPT_FOR_NOTIFICATION_LOGS, "*");
}
public static String delayedNotificationsPromptConfig() {
return getString(PROMPT_FOR_NOTIFICATION_CONFIG, "");
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View File

@@ -65,6 +65,9 @@ public final class LocaleFeatureFlags {
return isEnabled(FeatureFlags.PAYPAL_DISABLED_REGIONS, FeatureFlags.paypalDisabledRegions());
}
public static boolean isDelayedNotificationPromptEnabled() {
return isEnabled(FeatureFlags.PROMPT_FOR_NOTIFICATION_LOGS, FeatureFlags.promptForDelayedNotificationLogs());
}
/**
* 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

@@ -33,6 +33,11 @@ public final class NetworkUtil {
return info != null && info.isConnected() && info.isRoaming() && info.getType() == ConnectivityManager.TYPE_MOBILE;
}
public static boolean isConnected(@NonNull Context context) {
final NetworkInfo info = getNetworkInfo(context);
return info != null && info.isConnected();
}
public static @NonNull CallManager.DataMode getCallingDataMode(@NonNull Context context) {
return getCallingDataMode(context, PeerConnection.AdapterType.UNKNOWN);
}

View File

@@ -0,0 +1,44 @@
<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="M13.5,19.8C13.5,16.02 13.5,14.13 14.236,12.686C14.883,11.415 15.915,10.383 17.186,9.736C18.629,9 20.52,9 24.3,9H47.7C51.48,9 53.37,9 54.814,9.736C56.084,10.383 57.117,11.415 57.764,12.686C58.5,14.13 58.5,16.02 58.5,19.8V56.7C58.5,60.48 58.5,62.37 57.764,63.814C57.117,65.085 56.084,66.117 54.814,66.764C53.37,67.5 51.48,67.5 47.7,67.5H24.3C20.52,67.5 18.629,67.5 17.186,66.764C15.915,66.117 14.883,65.085 14.236,63.814C13.5,62.37 13.5,60.48 13.5,56.7V19.8Z"
android:fillColor="#DDE7FF"/>
<path
android:pathData="M17.901,9.442L53.942,67.107C52.592,67.5 50.769,67.5 47.7,67.5H24.3C20.52,67.5 18.629,67.5 17.186,66.764C15.915,66.117 14.883,65.085 14.236,63.814C13.5,62.37 13.5,60.48 13.5,56.7V19.8C13.5,16.02 13.5,14.13 14.236,12.686C14.883,11.415 15.915,10.383 17.186,9.736C17.411,9.621 17.648,9.524 17.901,9.442Z"
android:fillColor="#C7D1E5"/>
<path
android:pathData="M19.5,43.5C19.5,42.672 20.172,42 21,42L51,42C51.828,42 52.5,42.672 52.5,43.5C52.5,44.328 51.828,45 51,45L21,45C20.172,45 19.5,44.328 19.5,43.5Z"
android:fillColor="#8997B5"
android:fillType="evenOdd"/>
<path
android:pathData="M19.5,50.25C19.5,49.422 20.172,48.75 21,48.75L51,48.75C51.828,48.75 52.5,49.422 52.5,50.25C52.5,51.078 51.828,51.75 51,51.75L21,51.75C20.172,51.75 19.5,51.078 19.5,50.25Z"
android:fillColor="#8997B5"
android:fillType="evenOdd"/>
<path
android:pathData="M19.5,57C19.5,56.172 20.172,55.5 21,55.5L51,55.5C51.828,55.5 52.5,56.172 52.5,57C52.5,57.828 51.828,58.5 51,58.5L21,58.5C20.172,58.5 19.5,57.828 19.5,57Z"
android:fillColor="#8997B5"
android:fillType="evenOdd"/>
<path
android:pathData="M24.235,7.5H47.765C49.6,7.5 51.069,7.5 52.256,7.597C53.474,7.696 54.527,7.906 55.495,8.399C57.048,9.19 58.31,10.452 59.101,12.005C59.594,12.973 59.804,14.026 59.903,15.245C60,16.431 60,17.9 60,19.735V56.765C60,58.6 60,60.069 59.903,61.256C59.804,62.474 59.594,63.527 59.101,64.495C58.31,66.048 57.048,67.31 55.495,68.101C54.527,68.594 53.474,68.803 52.256,68.903C51.069,69 49.6,69 47.765,69H24.235C22.4,69 20.931,69 19.744,68.903C18.526,68.803 17.473,68.594 16.505,68.101C14.952,67.31 13.69,66.048 12.899,64.495C12.406,63.527 12.196,62.474 12.097,61.256C12,60.069 12,58.6 12,56.765V19.735C12,17.9 12,16.431 12.097,15.245C12.196,14.026 12.406,12.973 12.899,12.005C13.69,10.452 14.952,9.19 16.505,8.399C17.473,7.906 18.526,7.696 19.744,7.597C20.931,7.5 22.4,7.5 24.235,7.5ZM19.989,10.587C18.956,10.671 18.342,10.83 17.867,11.072C16.879,11.576 16.076,12.379 15.572,13.367C15.33,13.842 15.171,14.456 15.087,15.489C15.001,16.539 15,17.885 15,19.8V56.7C15,58.615 15.001,59.961 15.087,61.011C15.171,62.044 15.33,62.658 15.572,63.133C16.076,64.121 16.879,64.924 17.867,65.428C18.342,65.67 18.956,65.829 19.989,65.913C21.039,65.999 22.385,66 24.3,66H47.7C49.615,66 50.961,65.999 52.011,65.913C53.044,65.829 53.658,65.67 54.133,65.428C55.121,64.924 55.924,64.121 56.428,63.133C56.67,62.658 56.829,62.044 56.913,61.011C56.999,59.961 57,58.615 57,56.7V19.8C57,17.885 56.999,16.539 56.913,15.489C56.829,14.456 56.67,13.842 56.428,13.367C55.924,12.379 55.121,11.576 54.133,11.072C53.658,10.83 53.044,10.671 52.011,10.587C50.961,10.501 49.615,10.5 47.7,10.5H24.3C22.385,10.5 21.039,10.501 19.989,10.587Z"
android:fillColor="#6C7B9D"
android:fillType="evenOdd"/>
<path
android:pathData="M30.75,2.25C30.75,1.007 31.757,0 33,0H39C40.243,0 41.25,1.007 41.25,2.25V5.25H45.75C46.993,5.25 48,6.257 48,7.5V11.25C48,12.493 46.993,13.5 45.75,13.5H26.25C25.007,13.5 24,12.493 24,11.25V7.5C24,6.257 25.007,5.25 26.25,5.25H30.75V2.25ZM36,5.5C36.966,5.5 37.75,4.716 37.75,3.75C37.75,2.783 36.966,2 36,2C35.034,2 34.25,2.783 34.25,3.75C34.25,4.716 35.034,5.5 36,5.5Z"
android:fillColor="#4C5876"
android:fillType="evenOdd"/>
<path
android:pathData="M24,10.5H48V11.25C48,12.493 46.993,13.5 45.75,13.5H26.25C25.007,13.5 24,12.493 24,11.25V10.5Z"
android:fillColor="#8997B5"/>
<path
android:pathData="M34.051,19.125C34.917,17.625 37.083,17.625 37.949,19.125L45.743,32.625C46.609,34.125 45.526,36 43.794,36H28.206C26.474,36 25.391,34.125 26.257,32.625L34.051,19.125Z"
android:fillColor="#FA902E"/>
<path
android:pathData="M36,32.375m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#FFE3A5"/>
<path
android:pathData="M36,22.25C36.842,22.25 37.505,22.97 37.435,23.81L37.018,28.813C36.974,29.343 36.531,29.75 36,29.75C35.469,29.75 35.026,29.343 34.982,28.813L34.565,23.81C34.495,22.97 35.158,22.25 36,22.25Z"
android:fillColor="#FFE3A5"/>
</vector>

View File

@@ -0,0 +1,87 @@
<?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_debug_log"
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/PromptLogsSlowNotificationsDialog__title"
/>
<TextView
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/PromptLogsSlowNotificationsDialog__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/decline"
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/DebugLogsPromptDialogFragment__no_thanks"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/submit"
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/DebugLogsPromptDialogFragment__submit"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>

View File

@@ -905,6 +905,11 @@
<item quantity="other">%1$d members</item>
</plurals>
<!-- Title for dialog asking user to submit logs for debugging slow notification issues -->
<string name="PromptLogsSlowNotificationsDialog__title">We noticed notifications are delayed. Submit debug log?</string>
<!-- 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>
<!-- PendingMembersActivity -->
<string name="PendingMembersActivity_pending_group_invites">Pending group invites</string>
<string name="PendingMembersActivity_requests">Requests</string>
@@ -2866,6 +2871,14 @@
<item>Donations &amp; Badges</item>
<item>SMS Export</item>
</string-array>
<!-- Subject of email when submitting debug logs to help debug slow notifications -->
<string name="DebugLogsPromptDialogFragment__signal_android_support_request">Signal Android Debug Log Submission</string>
<!-- Category to organize the support email sent -->
<string name="DebugLogsPromptDialogFragment__slow_notifications_category">Slow notifications</string>
<!-- Action to submit logs and take user to send an e-mail -->
<string name="DebugLogsPromptDialogFragment__submit">Submit</string>
<!-- Action to decline to submit logs -->
<string name="DebugLogsPromptDialogFragment__no_thanks">No thanks</string>
<!-- ReactWithAnyEmojiBottomSheetDialogFragment -->
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__this_message">This Message</string>