Update copy and behavior of SMS phased removal flow.

This commit is contained in:
Cody Henthorne
2023-01-18 12:51:03 -05:00
parent 8f49323648
commit 6e2e5e21cc
26 changed files with 445 additions and 186 deletions

View File

@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.components.settings.app.chats.sms.SmsExportSta
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@@ -46,7 +45,7 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
private fun getConfiguration(state: ChatsSettingsState): DSLConfiguration {
return configure {
if (!state.useAsDefaultSmsApp && SignalStore.misc().smsExportPhase.isAtLeastPhase1()) {
if (!state.useAsDefaultSmsApp) {
when (state.smsExportState) {
SmsExportState.FETCHING -> Unit
SmsExportState.HAS_UNEXPORTED_MESSAGES -> {

View File

@@ -18,8 +18,6 @@ import org.thoughtcrime.securesms.components.settings.models.OutlinedLearnMore
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase
import org.thoughtcrime.securesms.util.SmsUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
@@ -56,16 +54,14 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
SignalStore.settings().setDefaultSms(true)
} else {
SignalStore.settings().setDefaultSms(false)
if (SignalStore.misc().smsExportPhase.isAtLeastPhase1()) {
findNavController().navigateUp()
}
findNavController().navigateUp()
}
}
private fun getConfiguration(state: SmsSettingsState): DSLConfiguration {
return configure {
if (state.useAsDefaultSmsApp && SignalStore.misc().smsExportPhase.isAtLeastPhase1()) {
if (state.useAsDefaultSmsApp) {
customPref(
OutlinedLearnMore.Model(
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging),
@@ -112,17 +108,13 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
SmsExportState.NOT_AVAILABLE -> Unit
}
if (state.useAsDefaultSmsApp || SignalStore.misc().smsExportPhase == SmsExportPhase.PHASE_0) {
if (state.useAsDefaultSmsApp) {
@Suppress("DEPRECATION")
clickPref(
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
summary = DSLSettingsText.from(if (state.useAsDefaultSmsApp) R.string.arrays__enabled else R.string.arrays__disabled),
summary = DSLSettingsText.from(R.string.arrays__enabled),
onClick = {
if (state.useAsDefaultSmsApp) {
startDefaultAppSelectionIntent()
} else {
startActivityForResult(SmsUtil.getSmsRoleIntent(requireContext()), SMS_REQUEST_CODE.toInt())
}
startDefaultAppSelectionIntent()
}
)
}

View File

@@ -5,17 +5,12 @@ import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.util.FeatureFlags
class SmsSettingsRepository(
private val smsDatabase: MessageTable = SignalDatabase.messages,
private val mmsDatabase: MessageTable = SignalDatabase.messages
) {
fun getSmsExportState(): Single<SmsExportState> {
if (!FeatureFlags.smsExporter()) {
return Single.just(SmsExportState.NOT_AVAILABLE)
}
return Single.fromCallable {
checkInsecureMessageCount() ?: checkUnexportedInsecureMessageCount()
}.subscribeOn(Schedulers.io())

View File

@@ -2747,13 +2747,7 @@ public class ConversationParentFragment extends Fragment
MaterialButton actionButton = smsExportStub.get().findViewById(R.id.export_sms_button);
boolean isPhase1 = SignalStore.misc().getSmsExportPhase() == SmsExportPhase.PHASE_1;
if (SignalStore.misc().getSmsExportPhase() == SmsExportPhase.PHASE_0) {
message.setText(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, recipient.getDisplayName(requireContext())));
actionButton.setText(R.string.conversation_activity__enable_signal_for_sms);
actionButton.setOnClickListener(v -> {
handleMakeDefaultSms();
});
} else if (conversationSecurityInfo.getHasUnexportedInsecureMessages()) {
if (conversationSecurityInfo.getHasUnexportedInsecureMessages()) {
message.setText(isPhase1 ? R.string.ConversationActivity__sms_messaging_is_currently_disabled_you_can_export_your_messages_to_another_app_on_your_phone
: R.string.ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_you_can_export_your_messages_to_another_app_on_your_phone);
actionButton.setText(R.string.ConversationActivity__export_sms_messages);

View File

@@ -444,9 +444,7 @@ public class ConversationViewModel extends ViewModel {
}
public void insertSmsExportUpdateEvent(@NonNull Recipient recipient) {
if (SignalStore.misc().getSmsExportPhase().isAtLeastPhase1()) {
conversationRepository.insertSmsExportUpdateEvent(recipient);
}
conversationRepository.insertSmsExportUpdateEvent(recipient);
}
enum Event {

View File

@@ -220,7 +220,8 @@ final class MenuState {
messageRecord.isChangeNumber() ||
messageRecord.isBoostRequest() ||
messageRecord.isPaymentsRequestToActivate() ||
messageRecord.isPaymentsActivated();
messageRecord.isPaymentsActivated() ||
messageRecord.isSmsExportType();
}
private final static class Builder {

View File

@@ -29,7 +29,7 @@ class SmsExportActivity : FragmentWrapperActivity() {
super.onCreate(savedInstanceState, ready)
onBackPressedDispatcher.addCallback(this, OnBackPressed())
val factory = SmsExportViewModel.Factory(intent.getBooleanExtra(IS_RE_EXPORT, false))
val factory = SmsExportViewModel.Factory(intent.getBooleanExtra(IS_FROM_MEGAPHONE, false), intent.getBooleanExtra(IS_RE_EXPORT, false))
viewModel = ViewModelProvider(this, factory).get(SmsExportViewModel::class.java)
}
@@ -46,13 +46,15 @@ class SmsExportActivity : FragmentWrapperActivity() {
}
companion object {
const val IS_RE_EXPORT = "is_re_export"
private const val IS_RE_EXPORT = "is_re_export"
private const val IS_FROM_MEGAPHONE = "is_from_megaphone"
@JvmOverloads
@JvmStatic
fun createIntent(context: Context, isReExport: Boolean = false): Intent {
fun createIntent(context: Context, isFromMegaphone: Boolean = false, isReExport: Boolean = false): Intent {
return Intent(context, SmsExportActivity::class.java).apply {
putExtra(IS_RE_EXPORT, isReExport)
putExtra(IS_FROM_MEGAPHONE, isFromMegaphone)
}
}
}

View File

@@ -8,10 +8,10 @@ import androidx.lifecycle.ViewModelProvider
*
* Note: Will be expanded on eventually to support different behavior when entering via megaphone.
*/
class SmsExportViewModel(val isReExport: Boolean) : ViewModel() {
class Factory(private val isReExport: Boolean) : ViewModelProvider.Factory {
class SmsExportViewModel(val isFromMegaphone: Boolean, val isReExport: Boolean) : ViewModel() {
class Factory(private val isFromMegaphone: Boolean, private val isReExport: Boolean) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(SmsExportViewModel(isReExport)))
return requireNotNull(modelClass.cast(SmsExportViewModel(isFromMegaphone, isReExport)))
}
}
}

View File

@@ -0,0 +1,54 @@
package org.thoughtcrime.securesms.exporter.flow
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.SmsRemovalInformationFragmentBinding
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
* Fragment shown when entering the sms export flow from the basic megaphone.
*
* Layout shared with full screen megaphones for Phase 2/3.
*/
class SmsRemovalInformationFragment : LoggingFragment() {
private val viewModel: SmsExportViewModel by activityViewModels()
private lateinit var binding: SmsRemovalInformationFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = SmsRemovalInformationFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (!viewModel.isFromMegaphone) {
findNavController().safeNavigate(SmsRemovalInformationFragmentDirections.actionSmsRemovalInformationFragmentToExportYourSmsMessagesFragment())
} else {
val goBackClickListener = { _: View ->
if (!findNavController().popBackStack()) {
requireActivity().finish()
}
}
binding.toolbar.setNavigationOnClickListener(goBackClickListener)
binding.laterButton.setOnClickListener(goBackClickListener)
binding.learnMoreButton.setOnClickListener {
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.sms_export_url))
}
binding.exportSmsButton.setOnClickListener {
findNavController().safeNavigate(SmsRemovalInformationFragmentDirections.actionSmsRemovalInformationFragmentToExportYourSmsMessagesFragment())
}
}
}
}

View File

@@ -128,8 +128,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
viewModel.getIsMms().observe(getViewLifecycleOwner(), isMms -> {
disappearingMessagesRow.setVisibility(isMms ? View.GONE : View.VISIBLE);
mmsWarning.setVisibility(isMms ? View.VISIBLE : View.GONE);
mmsWarningText.setText(SignalStore.misc().getSmsExportPhase().isAtLeastPhase1() ? R.string.AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support_signal_groups_mms_removal
: R.string.AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support);
mmsWarningText.setText(R.string.AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support_signal_groups_mms_removal);
name.setHint(isMms ? R.string.AddGroupDetailsFragment__group_name_optional : R.string.AddGroupDetailsFragment__group_name_required);
toolbar.setTitle(isMms ? R.string.AddGroupDetailsFragment__create_group : R.string.AddGroupDetailsFragment__name_this_group);
});

View File

@@ -232,6 +232,8 @@ public final class MiscellaneousValues extends SignalStoreValues {
}
public @NonNull SmsExportPhase getSmsExportPhase() {
return SmsExportPhase.PHASE_0;
long now = System.currentTimeMillis();
long phase1StartMs = getLong(SMS_PHASE_1_START_MS, now);
return SmsExportPhase.getCurrentPhase(now - phase1StartMs);
}
}

View File

@@ -5,13 +5,12 @@ import org.thoughtcrime.securesms.util.Util
import kotlin.time.Duration.Companion.days
enum class SmsExportPhase(val duration: Long) {
PHASE_0(-1),
PHASE_1(0.days.inWholeMilliseconds),
PHASE_2(45.days.inWholeMilliseconds),
PHASE_3(105.days.inWholeMilliseconds);
fun allowSmsFeatures(): Boolean {
return this == PHASE_0 || (Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()) && SignalStore.misc().smsExportPhase.isSmsSupported())
return Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()) && SignalStore.misc().smsExportPhase.isSmsSupported()
}
fun isSmsSupported(): Boolean {
@@ -26,10 +25,6 @@ enum class SmsExportPhase(val duration: Long) {
return this == PHASE_3
}
fun isAtLeastPhase1(): Boolean {
return this.ordinal >= PHASE_1.ordinal
}
companion object {
@JvmStatic
fun getCurrentPhase(duration: Long): SmsExportPhase {

View File

@@ -362,14 +362,15 @@ public final class Megaphones {
private static @NonNull Megaphone buildSmsExportMegaphone(@NonNull Context context) {
SmsExportPhase phase = SignalStore.misc().getSmsExportPhase();
if (phase == SmsExportPhase.PHASE_0) {
throw new AssertionError("Should not be showing megaphone for Phase 0");
} else if (phase == SmsExportPhase.PHASE_1) {
if (phase == SmsExportPhase.PHASE_1) {
return new Megaphone.Builder(Event.SMS_EXPORT, Megaphone.Style.BASIC)
.setTitle(R.string.SmsExportMegaphone__sms_support_going_away)
.setImage(R.drawable.sms_megaphone)
.setBody(R.string.SmsExportMegaphone__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging)
.setActionButton(R.string.SmsExportMegaphone__export_sms, (megaphone, controller) -> controller.onMegaphoneNavigationRequested(SmsExportActivity.createIntent(context), SmsExportMegaphoneActivity.REQUEST_CODE))
.setBody(R.string.SmsExportMegaphone__dont_worry_encrypted_signal_messages_will_continue_to_work)
.setActionButton(R.string.SmsExportMegaphone__continue, (megaphone, controller) -> {
controller.onMegaphoneSnooze(Event.SMS_EXPORT);
controller.onMegaphoneNavigationRequested(SmsExportActivity.createIntent(context, true), SmsExportMegaphoneActivity.REQUEST_CODE);
})
.setSecondaryButton(R.string.Megaphones_remind_me_later, (megaphone, controller) -> controller.onMegaphoneSnooze(Event.SMS_EXPORT))
.setOnVisibleListener((megaphone, controller) -> SignalStore.misc().startSmsPhase1())
.build();

View File

@@ -7,13 +7,14 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.SmsExportMegaphoneActivityBinding
import org.thoughtcrime.securesms.databinding.SmsRemovalInformationFragmentBinding
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.visible
class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
@@ -22,7 +23,7 @@ class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
}
private val theme: DynamicTheme = DynamicNoActionBarTheme()
private lateinit var binding: SmsExportMegaphoneActivityBinding
private lateinit var binding: SmsRemovalInformationFragmentBinding
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
override fun onPreCreate() {
@@ -30,7 +31,7 @@ class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
binding = SmsExportMegaphoneActivityBinding.inflate(layoutInflater)
binding = SmsRemovalInformationFragmentBinding.inflate(layoutInflater)
setContentView(binding.root)
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@@ -43,26 +44,22 @@ class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
binding.learnMoreButton.setOnClickListener {
CommunicationActions.openBrowserLink(this, getString(R.string.sms_export_url))
}
if (SignalStore.misc().smsExportPhase.isBlockingUi()) {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_no_longer_supports_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_has_removed_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(false)
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__learn_more)
binding.laterButton.setOnClickListener {
CommunicationActions.openBrowserLink(this, getString(R.string.sms_export_url))
}
binding.laterButton.visible = false
binding.bullet1Text.setText(R.string.SmsRemoval_info_bullet_1_phase_3)
} else {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_will_no_longer_support_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_will_soon_remove_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(true)
binding.description.setLink(getString(R.string.sms_export_url))
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__remind_me_later)
binding.laterButton.setOnClickListener {
onBackPressed()
}
}
binding.exportButton.setOnClickListener {
binding.exportSmsButton.setOnClickListener {
smsExportLauncher.launch(SmsExportActivity.createIntent(this))
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase
import org.thoughtcrime.securesms.util.Util
import kotlin.time.Duration.Companion.days
class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedule {
@@ -18,9 +19,8 @@ class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedul
@WorkerThread
override fun shouldDisplay(seenCount: Int, lastSeen: Long, firstVisible: Long, currentTime: Long): Boolean {
return if (shouldShowMegaphone()) {
return if (Util.isDefaultSmsProvider(context)) {
when (SignalStore.misc().smsExportPhase) {
SmsExportPhase.PHASE_0 -> false
SmsExportPhase.PHASE_1 -> basicMegaphoneSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_2 -> fullScreenSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_3 -> showPhase3Megaphone
@@ -29,9 +29,4 @@ class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedul
false
}
}
@WorkerThread
private fun shouldShowMegaphone(): Boolean {
return false
}
}

View File

@@ -94,7 +94,6 @@ public final class FeatureFlags {
private static final String CAMERAX_MODEL_BLOCKLIST = "android.cameraXModelBlockList";
private static final String CAMERAX_MIXED_MODEL_BLOCKLIST = "android.cameraXMixedModelBlockList";
private static final String RECIPIENT_MERGE_V2 = "android.recipientMergeV2";
private static final String SMS_EXPORTER = "android.sms.exporter.2";
private static final String HIDE_CONTACTS = "android.hide.contacts";
public static final String CREDIT_CARD_PAYMENTS = "android.credit.card.payments.3";
private static final String PAYMENTS_REQUEST_ACTIVATE_FLOW = "android.payments.requestActivateFlow";
@@ -150,7 +149,6 @@ public final class FeatureFlags {
CAMERAX_MODEL_BLOCKLIST,
CAMERAX_MIXED_MODEL_BLOCKLIST,
RECIPIENT_MERGE_V2,
SMS_EXPORTER,
HIDE_CONTACTS,
CREDIT_CARD_PAYMENTS,
PAYMENTS_REQUEST_ACTIVATE_FLOW,
@@ -495,16 +493,6 @@ public final class FeatureFlags {
return getInteger(STORIES_AUTO_DOWNLOAD_MAXIMUM, 2);
}
/**
* Whether or not we should enable the SMS exporter
*
* WARNING: This feature is under active development and is off for a reason. The exporter writes messages out to your
* system SMS / MMS database, and hasn't been adequately tested for public use. Don't enable this. You've been warned.
*/
public static boolean smsExporter() {
return getBoolean(SMS_EXPORTER, false);
}
/**
* Whether or not users can hide contacts.
*