mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-26 22:20:20 +00:00
Remove SMS export.
This commit is contained in:
committed by
Nicholas Tinsley
parent
98865d61dd
commit
aa33fd44b8
@@ -1,26 +1,18 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.sms.SmsExportState
|
||||
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.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__chats) {
|
||||
|
||||
private lateinit var viewModel: ChatsSettingsViewModel
|
||||
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
@@ -29,12 +21,6 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
||||
|
||||
@Suppress("ReplaceGetOrSet")
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
|
||||
}
|
||||
}
|
||||
|
||||
viewModel = ViewModelProvider(this).get(ChatsSettingsViewModel::class.java)
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
@@ -44,55 +30,6 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
||||
|
||||
private fun getConfiguration(state: ChatsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
if (!state.useAsDefaultSmsApp) {
|
||||
when (state.smsExportState) {
|
||||
SmsExportState.FETCHING -> Unit
|
||||
SmsExportState.HAS_UNEXPORTED_MESSAGES -> {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__export_sms_messages),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__you_can_export_your_sms_messages_to_your_phones_sms_database),
|
||||
onClick = {
|
||||
smsExportLauncher.launch(SmsExportActivity.createIntent(requireContext()))
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
SmsExportState.ALL_MESSAGES_EXPORTED -> {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__remove_sms_messages),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__remove_sms_messages_from_signal_to_clear_up_storage_space),
|
||||
onClick = {
|
||||
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__export_sms_messages_again),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__exporting_again_can_result_in_duplicate_messages),
|
||||
onClick = {
|
||||
SmsExportDialogs.showSmsReExportDialog(requireContext()) {
|
||||
smsExportLauncher.launch(SmsExportActivity.createIntent(requireContext(), isReExport = true))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
|
||||
SmsExportState.NOT_AVAILABLE -> Unit
|
||||
}
|
||||
} else {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__sms_mms),
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).safeNavigate(R.id.action_chatsSettingsFragment_to_smsSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__generate_link_previews),
|
||||
summary = DSLSettingsText.from(R.string.preferences__retrieve_link_previews_from_websites_for_messages),
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats
|
||||
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.sms.SmsExportState
|
||||
|
||||
data class ChatsSettingsState(
|
||||
val generateLinkPreviews: Boolean,
|
||||
val useAddressBook: Boolean,
|
||||
val keepMutedChatsArchived: Boolean,
|
||||
val useSystemEmoji: Boolean,
|
||||
val enterKeySends: Boolean,
|
||||
val chatBackupsEnabled: Boolean,
|
||||
val useAsDefaultSmsApp: Boolean,
|
||||
val smsExportState: SmsExportState = SmsExportState.FETCHING
|
||||
val chatBackupsEnabled: Boolean
|
||||
)
|
||||
|
||||
@@ -2,24 +2,18 @@ package org.thoughtcrime.securesms.components.settings.app.chats
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.sms.SmsSettingsRepository
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.ThrottledDebouncer
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class ChatsSettingsViewModel @JvmOverloads constructor(
|
||||
private val repository: ChatsSettingsRepository = ChatsSettingsRepository(),
|
||||
smsSettingsRepository: SmsSettingsRepository = SmsSettingsRepository()
|
||||
private val repository: ChatsSettingsRepository = ChatsSettingsRepository()
|
||||
) : ViewModel() {
|
||||
|
||||
private val refreshDebouncer = ThrottledDebouncer(500L)
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val store: Store<ChatsSettingsState> = Store(
|
||||
ChatsSettingsState(
|
||||
@@ -28,23 +22,12 @@ class ChatsSettingsViewModel @JvmOverloads constructor(
|
||||
keepMutedChatsArchived = SignalStore.settings().shouldKeepMutedChatsArchived(),
|
||||
useSystemEmoji = SignalStore.settings().isPreferSystemEmoji,
|
||||
enterKeySends = SignalStore.settings().isEnterKeySends,
|
||||
chatBackupsEnabled = SignalStore.settings().isBackupEnabled && BackupUtil.canUserAccessBackupDirectory(ApplicationDependencies.getApplication()),
|
||||
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication())
|
||||
chatBackupsEnabled = SignalStore.settings().isBackupEnabled && BackupUtil.canUserAccessBackupDirectory(ApplicationDependencies.getApplication())
|
||||
)
|
||||
)
|
||||
|
||||
val state: LiveData<ChatsSettingsState> = store.stateLiveData
|
||||
|
||||
init {
|
||||
disposables += smsSettingsRepository.getSmsExportState().subscribe { state ->
|
||||
store.update { it.copy(smsExportState = state) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
fun setGenerateLinkPreviewsEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(generateLinkPreviews = enabled) }
|
||||
SignalStore.settings().isLinkPreviewsEnabled = enabled
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
enum class SmsExportState {
|
||||
FETCHING,
|
||||
HAS_UNEXPORTED_MESSAGES,
|
||||
ALL_MESSAGES_EXPORTED,
|
||||
NO_SMS_MESSAGES_IN_DATABASE,
|
||||
NOT_AVAILABLE
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
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.util.Util
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
||||
private const val SMS_REQUEST_CODE: Short = 1234
|
||||
|
||||
class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
||||
|
||||
private lateinit var viewModel: SmsSettingsViewModel
|
||||
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.checkSmsEnabled()
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
OutlinedLearnMore.register(adapter)
|
||||
|
||||
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
|
||||
}
|
||||
}
|
||||
|
||||
viewModel = ViewModelProvider(this)[SmsSettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(getConfiguration(it).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (Util.isDefaultSmsProvider(requireContext())) {
|
||||
SignalStore.settings().setDefaultSms(true)
|
||||
} else {
|
||||
SignalStore.settings().setDefaultSms(false)
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: SmsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
if (state.useAsDefaultSmsApp) {
|
||||
customPref(
|
||||
OutlinedLearnMore.Model(
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging),
|
||||
learnMoreUrl = getString(R.string.sms_export_url)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
when (state.smsExportState) {
|
||||
SmsExportState.FETCHING -> Unit
|
||||
SmsExportState.HAS_UNEXPORTED_MESSAGES -> {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__export_sms_messages),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__you_can_export_your_sms_messages_to_your_phones_sms_database),
|
||||
onClick = {
|
||||
smsExportLauncher.launch(SmsExportActivity.createIntent(requireContext()))
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
SmsExportState.ALL_MESSAGES_EXPORTED -> {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__remove_sms_messages),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__remove_sms_messages_from_signal_to_clear_up_storage_space),
|
||||
onClick = {
|
||||
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__export_sms_messages_again),
|
||||
summary = DSLSettingsText.from(R.string.SmsSettingsFragment__exporting_again_can_result_in_duplicate_messages),
|
||||
onClick = {
|
||||
SmsExportDialogs.showSmsReExportDialog(requireContext()) {
|
||||
smsExportLauncher.launch(SmsExportActivity.createIntent(requireContext(), isReExport = true))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
|
||||
SmsExportState.NOT_AVAILABLE -> Unit
|
||||
}
|
||||
|
||||
if (state.useAsDefaultSmsApp) {
|
||||
@Suppress("DEPRECATION")
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
|
||||
summary = DSLSettingsText.from(R.string.arrays__enabled),
|
||||
onClick = {
|
||||
startDefaultAppSelectionIntent()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__sms_delivery_reports),
|
||||
summary = DSLSettingsText.from(R.string.preferences__request_a_delivery_report_for_each_sms_message_you_send),
|
||||
isChecked = state.smsDeliveryReportsEnabled,
|
||||
onClick = {
|
||||
viewModel.setSmsDeliveryReportsEnabled(!state.smsDeliveryReportsEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__support_wifi_calling),
|
||||
summary = DSLSettingsText.from(R.string.preferences__enable_if_your_device_supports_sms_mms_delivery_over_wifi),
|
||||
isChecked = state.wifiCallingCompatibilityEnabled,
|
||||
onClick = {
|
||||
viewModel.setWifiCallingCompatibilityEnabled(!state.wifiCallingCompatibilityEnabled)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Linter isn't smart enough to figure out the else only happens if API >= 24
|
||||
@SuppressLint("InlinedApi")
|
||||
@Suppress("DEPRECATION")
|
||||
private fun startDefaultAppSelectionIntent() {
|
||||
val intent: Intent = when {
|
||||
Build.VERSION.SDK_INT < 23 -> Intent(Settings.ACTION_WIRELESS_SETTINGS)
|
||||
Build.VERSION.SDK_INT < 24 -> Intent(Settings.ACTION_SETTINGS)
|
||||
else -> Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
|
||||
}
|
||||
|
||||
startActivityForResult(intent, SMS_REQUEST_CODE.toInt())
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
class SmsSettingsRepository(
|
||||
private val smsDatabase: MessageTable = SignalDatabase.messages,
|
||||
private val mmsDatabase: MessageTable = SignalDatabase.messages
|
||||
) {
|
||||
fun getSmsExportState(): Single<SmsExportState> {
|
||||
return Single.fromCallable {
|
||||
checkInsecureMessageCount() ?: checkUnexportedInsecureMessageCount()
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun checkInsecureMessageCount(): SmsExportState? {
|
||||
val totalSmsMmsCount = smsDatabase.getInsecureMessageCount() + mmsDatabase.getInsecureMessageCount()
|
||||
|
||||
return if (totalSmsMmsCount == 0) {
|
||||
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun checkUnexportedInsecureMessageCount(): SmsExportState {
|
||||
val totalUnexportedCount = smsDatabase.getUnexportedInsecureMessagesCount() + mmsDatabase.getUnexportedInsecureMessagesCount()
|
||||
|
||||
return if (totalUnexportedCount > 0) {
|
||||
SmsExportState.HAS_UNEXPORTED_MESSAGES
|
||||
} else {
|
||||
SmsExportState.ALL_MESSAGES_EXPORTED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
data class SmsSettingsState(
|
||||
val useAsDefaultSmsApp: Boolean,
|
||||
val smsDeliveryReportsEnabled: Boolean,
|
||||
val wifiCallingCompatibilityEnabled: Boolean,
|
||||
val smsExportState: SmsExportState = SmsExportState.FETCHING
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class SmsSettingsViewModel : ViewModel() {
|
||||
|
||||
private val repository = SmsSettingsRepository()
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
private val store = Store(
|
||||
SmsSettingsState(
|
||||
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()),
|
||||
smsDeliveryReportsEnabled = SignalStore.settings().isSmsDeliveryReportsEnabled,
|
||||
wifiCallingCompatibilityEnabled = SignalStore.settings().isWifiCallingCompatibilityModeEnabled
|
||||
)
|
||||
)
|
||||
|
||||
val state: LiveData<SmsSettingsState> = store.stateLiveData
|
||||
|
||||
init {
|
||||
disposables += repository.getSmsExportState().subscribe { state ->
|
||||
store.update { it.copy(smsExportState = state) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
fun setSmsDeliveryReportsEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(smsDeliveryReportsEnabled = enabled) }
|
||||
SignalStore.settings().isSmsDeliveryReportsEnabled = enabled
|
||||
}
|
||||
|
||||
fun setWifiCallingCompatibilityEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(wifiCallingCompatibilityEnabled = enabled) }
|
||||
SignalStore.settings().isWifiCallingCompatibilityModeEnabled = enabled
|
||||
}
|
||||
|
||||
fun checkSmsEnabled() {
|
||||
store.update { it.copy(useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication())) }
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,6 @@ import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
@@ -140,7 +137,6 @@ import org.thoughtcrime.securesms.database.ThreadTable;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -152,12 +148,10 @@ import org.thoughtcrime.securesms.megaphone.Megaphone;
|
||||
import org.thoughtcrime.securesms.megaphone.MegaphoneActionController;
|
||||
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.megaphone.SmsExportMegaphoneActivity;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.profiles.manage.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment;
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -691,15 +685,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == SmsExportMegaphoneActivity.REQUEST_CODE) {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.SMS_EXPORT);
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
Snackbar.make(fab, R.string.ConversationActivity__you_will_be_reminded_again_soon, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
SmsExportDialogs.showSmsRemovalDialog(requireContext(), fab);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK && requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN) {
|
||||
Snackbar.make(fab, R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show();
|
||||
viewModel.onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL);
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter
|
||||
|
||||
import org.json.JSONException
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.smsexporter.ExportableMessage
|
||||
import org.signal.smsexporter.SmsExportState
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import java.io.Closeable
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Reads through the SMS and MMS databases for insecure messages that haven't been exported. Due to cursor size limitations
|
||||
* we "page" through the unexported messages to reduce chances of exceeding that limit.
|
||||
*/
|
||||
class SignalSmsExportReader(
|
||||
private val messageTable: MessageTable = SignalDatabase.messages
|
||||
) : Iterable<ExportableMessage>, Closeable {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(SignalSmsExportReader::class.java)
|
||||
private const val CURSOR_LIMIT = 1000
|
||||
}
|
||||
|
||||
private var messageReader: MessageTable.MmsReader? = null
|
||||
private var done: Boolean = false
|
||||
|
||||
override fun iterator(): Iterator<ExportableMessage> {
|
||||
return ExportableMessageIterator()
|
||||
}
|
||||
|
||||
fun getCount(): Int {
|
||||
return messageTable.getUnexportedInsecureMessagesCount()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
messageReader?.close()
|
||||
}
|
||||
|
||||
private fun refreshReaders() {
|
||||
if (!done) {
|
||||
messageReader?.close()
|
||||
messageReader = null
|
||||
|
||||
val refreshedMmsReader = MessageTable.mmsReaderFor(messageTable.getUnexportedInsecureMessages(CURSOR_LIMIT))
|
||||
if (refreshedMmsReader.getCount() > 0) {
|
||||
messageReader = refreshedMmsReader
|
||||
return
|
||||
} else {
|
||||
refreshedMmsReader.close()
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ExportableMessageIterator : Iterator<ExportableMessage> {
|
||||
|
||||
private var messageIterator: Iterator<MessageRecord>? = null
|
||||
|
||||
private fun refreshIterators() {
|
||||
refreshReaders()
|
||||
messageIterator = messageReader?.iterator()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (messageIterator?.hasNext() == true) {
|
||||
return true
|
||||
} else if (!done) {
|
||||
refreshIterators()
|
||||
if (messageIterator?.hasNext() == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun next(): ExportableMessage {
|
||||
var record: MessageRecord? = null
|
||||
try {
|
||||
return if (messageIterator?.hasNext() == true) {
|
||||
record = messageIterator!!.next()
|
||||
readExportableMmsMessageFromRecord(record, messageReader!!.getMessageExportStateForCurrentRecord())
|
||||
} else {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e.cause is JSONException) {
|
||||
Log.w(TAG, "Error processing attachment json, skipping message.", e)
|
||||
return ExportableMessage.Skip(messageReader!!.getCurrentId())
|
||||
}
|
||||
|
||||
Log.w(TAG, "Error processing message: isMms: ${record?.isMms} type: ${record?.type}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun readExportableMmsMessageFromRecord(record: MessageRecord, exportState: MessageExportState): ExportableMessage {
|
||||
val self = Recipient.self()
|
||||
val threadRecipient: Recipient? = SignalDatabase.threads.getRecipientForThreadId(record.threadId)
|
||||
val addresses: Set<String> = if (threadRecipient?.isMmsGroup == true) {
|
||||
Recipient
|
||||
.resolvedList(threadRecipient.participantIds)
|
||||
.filter { it != self }
|
||||
.map { r -> r.smsExportAddress() }
|
||||
.toSet()
|
||||
} else if (threadRecipient != null) {
|
||||
setOf(threadRecipient.smsExportAddress())
|
||||
} else {
|
||||
setOf(record.toRecipient.smsExportAddress())
|
||||
}
|
||||
|
||||
val parts: MutableList<ExportableMessage.Mms.Part> = mutableListOf()
|
||||
if (record.body.isNotBlank()) {
|
||||
parts.add(ExportableMessage.Mms.Part.Text(record.body))
|
||||
}
|
||||
|
||||
if (record is MmsMessageRecord) {
|
||||
val slideDeck = record.slideDeck
|
||||
slideDeck
|
||||
.slides
|
||||
.filter { it.asAttachment() is DatabaseAttachment }
|
||||
.forEach {
|
||||
parts.add(
|
||||
ExportableMessage.Mms.Part.Stream(
|
||||
id = JsonUtils.toJson((it.asAttachment() as DatabaseAttachment).attachmentId),
|
||||
contentType = it.contentType
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val sender: String = if (record.isOutgoing) Recipient.self().smsExportAddress() else record.fromRecipient.smsExportAddress()
|
||||
|
||||
return ExportableMessage.Mms(
|
||||
id = MessageId(record.id),
|
||||
exportState = mapExportState(exportState),
|
||||
addresses = addresses,
|
||||
dateReceived = record.dateReceived.milliseconds,
|
||||
dateSent = record.dateSent.milliseconds,
|
||||
isRead = true,
|
||||
isOutgoing = record.isOutgoing,
|
||||
parts = parts,
|
||||
sender = sender
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapExportState(messageExportState: MessageExportState): SmsExportState {
|
||||
return SmsExportState(
|
||||
messageId = messageExportState.messageId,
|
||||
startedRecipients = messageExportState.startedRecipients.toSet(),
|
||||
completedRecipients = messageExportState.completedRecipients.toSet(),
|
||||
startedAttachments = messageExportState.startedAttachments.toSet(),
|
||||
completedAttachments = messageExportState.completedAttachments.toSet(),
|
||||
progress = messageExportState.progress.let {
|
||||
when (it) {
|
||||
MessageExportState.Progress.INIT -> SmsExportState.Progress.INIT
|
||||
MessageExportState.Progress.STARTED -> SmsExportState.Progress.STARTED
|
||||
MessageExportState.Progress.COMPLETED -> SmsExportState.Progress.COMPLETED
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun Recipient.smsExportAddress(): String {
|
||||
return smsAddress.orElseGet { getDisplayName(ApplicationDependencies.getApplication()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import app.cash.exhaustive.Exhaustive
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.smsexporter.ExportableMessage
|
||||
import org.signal.smsexporter.SmsExportService
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
|
||||
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.notifications.v2.NotificationPendingIntentHelper
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import java.io.EOFException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Service which integrates the SMS exporter functionality.
|
||||
*/
|
||||
class SignalSmsExportService : SmsExportService() {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Launches the export service and immediately begins exporting messages.
|
||||
*/
|
||||
fun start(context: Context, clearPreviousExportState: Boolean) {
|
||||
val intent = Intent(context, SignalSmsExportService::class.java)
|
||||
.apply { putExtra(CLEAR_PREVIOUS_EXPORT_STATE_EXTRA, clearPreviousExportState) }
|
||||
ForegroundServiceUtil.startOrThrow(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
private var reader: SignalSmsExportReader? = null
|
||||
|
||||
override fun getNotification(progress: Int, total: Int): ExportNotification {
|
||||
val pendingIntent = NotificationPendingIntentHelper.getActivity(
|
||||
this,
|
||||
0,
|
||||
SmsExportActivity.createIntent(this),
|
||||
PendingIntentFlags.mutable()
|
||||
)
|
||||
|
||||
return ExportNotification(
|
||||
NotificationIds.SMS_EXPORT_SERVICE,
|
||||
NotificationCompat.Builder(this, NotificationChannels.getInstance().BACKUPS)
|
||||
.setSmallIcon(R.drawable.ic_signal_backup)
|
||||
.setContentTitle(getString(R.string.SignalSmsExportService__exporting_messages))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setProgress(total, progress, false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExportCompleteNotification(): ExportNotification? {
|
||||
if (ApplicationDependencies.getAppForegroundObserver().isForegrounded) {
|
||||
return null
|
||||
}
|
||||
|
||||
val pendingIntent = NotificationPendingIntentHelper.getActivity(
|
||||
this,
|
||||
0,
|
||||
SmsExportActivity.createIntent(this),
|
||||
PendingIntentFlags.mutable()
|
||||
)
|
||||
|
||||
return ExportNotification(
|
||||
NotificationIds.SMS_EXPORT_COMPLETE,
|
||||
NotificationCompat.Builder(this, NotificationChannels.getInstance().APP_ALERTS)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(getString(R.string.SignalSmsExportService__signal_sms_export_complete))
|
||||
.setContentText(getString(R.string.SignalSmsExportService__tap_to_return_to_signal))
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun clearPreviousExportState() {
|
||||
SignalDatabase.messages.clearExportState()
|
||||
}
|
||||
|
||||
override fun prepareForExport() {
|
||||
SignalDatabase.messages.clearInsecureMessageExportedErrorStatus()
|
||||
}
|
||||
|
||||
override fun getUnexportedMessageCount(): Int {
|
||||
ensureReader()
|
||||
return reader!!.getCount()
|
||||
}
|
||||
|
||||
override fun getUnexportedMessages(): Iterable<ExportableMessage> {
|
||||
ensureReader()
|
||||
return reader!!
|
||||
}
|
||||
|
||||
override fun onMessageExportStarted(exportableMessage: ExportableMessage) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().progress(MessageExportState.Progress.STARTED).build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessageExportSucceeded(exportableMessage: ExportableMessage) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().progress(MessageExportState.Progress.COMPLETED).build()
|
||||
}
|
||||
|
||||
SignalDatabase.messages.markMessageExported(exportableMessage.getMessageId())
|
||||
}
|
||||
|
||||
override fun onMessageExportFailed(exportableMessage: ExportableMessage) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().progress(MessageExportState.Progress.INIT).build()
|
||||
}
|
||||
|
||||
SignalDatabase.messages.markMessageExportFailed(exportableMessage.getMessageId())
|
||||
}
|
||||
|
||||
override fun onMessageIdCreated(exportableMessage: ExportableMessage, messageId: Long) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().messageId(messageId).build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachmentPartExportStarted(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().apply { startedAttachments += part.contentId }.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachmentPartExportSucceeded(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().apply { completedAttachments += part.contentId }.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachmentPartExportFailed(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
val startedAttachments = it.startedAttachments - part.contentId
|
||||
it.newBuilder().startedAttachments(startedAttachments).build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRecipientExportStarted(exportableMessage: ExportableMessage, recipient: String) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().apply { startedRecipients += recipient }.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRecipientExportSucceeded(exportableMessage: ExportableMessage, recipient: String) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
it.newBuilder().apply { completedRecipients += recipient }.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRecipientExportFailed(exportableMessage: ExportableMessage, recipient: String) {
|
||||
SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) {
|
||||
val startedAttachments = it.startedRecipients - recipient
|
||||
it.newBuilder().startedRecipients(startedAttachments).build()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputStream(part: ExportableMessage.Mms.Part): InputStream {
|
||||
try {
|
||||
return SignalDatabase.attachments.getAttachmentStream(JsonUtils.fromJson(part.contentId, AttachmentId::class.java), 0)
|
||||
} catch (e: IOException) {
|
||||
if (e.message == ModernDecryptingPartInputStream.PREMATURE_END_ERROR_MESSAGE) {
|
||||
throw EOFException(e.message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExportPassCompleted() {
|
||||
reader?.close()
|
||||
}
|
||||
|
||||
private fun ExportableMessage.getMessageId(): MessageId {
|
||||
@Exhaustive
|
||||
val messageId: Any = when (this) {
|
||||
is ExportableMessage.Mms<*> -> id
|
||||
is ExportableMessage.Sms<*> -> id
|
||||
is ExportableMessage.Skip<*> -> id
|
||||
}
|
||||
|
||||
if (messageId is MessageId) {
|
||||
return messageId
|
||||
} else {
|
||||
throw AssertionError("Exportable message id must be type MessageId. Type: ${messageId.javaClass}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureReader() {
|
||||
if (reader == null) {
|
||||
reader = SignalSmsExportReader()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.smsexporter.DefaultSmsHelper
|
||||
import org.signal.smsexporter.ReleaseSmsAppFailure
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.ChooseANewDefaultSmsAppFragmentBinding
|
||||
|
||||
/**
|
||||
* Fragment which can launch the user into picking an alternative
|
||||
* SMS app, or give them instructions on how to do so manually.
|
||||
*/
|
||||
class ChooseANewDefaultSmsAppFragment : Fragment(R.layout.choose_a_new_default_sms_app_fragment) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ChooseANewDefaultSmsAppFragment::class.java)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = ChooseANewDefaultSmsAppFragmentBinding.bind(view)
|
||||
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
binding.bullet1Text.setText(R.string.ChooseANewDefaultSmsAppFragment__open_your_phones_settings_app)
|
||||
binding.bullet2Text.setText(R.string.ChooseANewDefaultSmsAppFragment__navigate_to_apps_default_apps_sms_app)
|
||||
binding.continueButton.setText(R.string.ChooseANewDefaultSmsAppFragment__done)
|
||||
}
|
||||
|
||||
DefaultSmsHelper.releaseDefaultSms(requireContext()).either(
|
||||
onSuccess = {
|
||||
binding.continueButton.setOnClickListener { _ -> startActivity(it) }
|
||||
},
|
||||
onFailure = {
|
||||
when (it) {
|
||||
ReleaseSmsAppFailure.APP_IS_INELIGIBLE_TO_RELEASE_SMS_SELECTION -> {
|
||||
Log.w(TAG, "App is ineligible to release sms selection")
|
||||
binding.continueButton.setOnClickListener { requireActivity().finish() }
|
||||
}
|
||||
ReleaseSmsAppFailure.NO_METHOD_TO_RELEASE_SMS_AVIALABLE -> {
|
||||
Log.w(TAG, "We can't navigate the user to a specific spot so we should display instructions instead.")
|
||||
binding.continueButton.setOnClickListener { requireActivity().finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!DefaultSmsHelper.isDefaultSms(requireContext())) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.SmsExportDirections
|
||||
import org.thoughtcrime.securesms.databinding.ExportSmsCompleteFragmentBinding
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Shown when export sms completes.
|
||||
*/
|
||||
class ExportSmsCompleteFragment : Fragment(R.layout.export_sms_complete_fragment) {
|
||||
|
||||
private val args: ExportSmsCompleteFragmentArgs by navArgs()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val exportSuccessCount = args.exportMessageCount - args.exportMessageFailureCount
|
||||
|
||||
val binding = ExportSmsCompleteFragmentBinding.bind(view)
|
||||
binding.exportCompleteNext.setOnClickListener { findNavController().safeNavigate(SmsExportDirections.actionDirectToChooseANewDefaultSmsAppFragment()) }
|
||||
binding.exportCompleteStatus.text = resources.getQuantityString(R.plurals.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, exportSuccessCount, args.exportMessageCount)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.SmsExportDirections
|
||||
import org.thoughtcrime.securesms.databinding.ExportSmsFullErrorFragmentBinding
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Fragment shown when all export messages failed.
|
||||
*/
|
||||
class ExportSmsFullErrorFragment : LoggingFragment(R.layout.export_sms_full_error_fragment) {
|
||||
private val args: ExportSmsFullErrorFragmentArgs by navArgs()
|
||||
|
||||
override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
|
||||
val inflater = super.onGetLayoutInflater(savedInstanceState)
|
||||
val contextThemeWrapper: Context = ContextThemeWrapper(requireContext(), R.style.Signal_DayNight)
|
||||
return inflater.cloneInContext(contextThemeWrapper)
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = ExportSmsFullErrorFragmentBinding.bind(view)
|
||||
|
||||
val exportSuccessCount = args.exportMessageCount - args.exportMessageFailureCount
|
||||
binding.exportCompleteStatus.text = resources.getQuantityString(R.plurals.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, exportSuccessCount, args.exportMessageCount)
|
||||
binding.retryButton.setOnClickListener { findNavController().safeNavigate(SmsExportDirections.actionDirectToExportYourSmsMessagesFragment()) }
|
||||
binding.pleaseTryAgain.apply {
|
||||
setLinkColor(ContextCompat.getColor(requireContext(), R.color.signal_colorPrimary))
|
||||
setLearnMoreVisible(true, R.string.ExportSmsPartiallyComplete__contact_us)
|
||||
setOnLinkClickListener {
|
||||
findNavController().safeNavigate(SmsExportDirections.actionDirectToHelpFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.signal.core.util.concurrent.SimpleTask
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.SmsExportDirections
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.databinding.ExportSmsPartiallyCompleteFragmentBinding
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Fragment shown when some messages exported and some failed.
|
||||
*/
|
||||
class ExportSmsPartiallyCompleteFragment : LoggingFragment(R.layout.export_sms_partially_complete_fragment) {
|
||||
|
||||
private val args: ExportSmsPartiallyCompleteFragmentArgs by navArgs()
|
||||
|
||||
override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
|
||||
val inflater = super.onGetLayoutInflater(savedInstanceState)
|
||||
val contextThemeWrapper: Context = ContextThemeWrapper(requireContext(), R.style.Signal_DayNight)
|
||||
return inflater.cloneInContext(contextThemeWrapper)
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = ExportSmsPartiallyCompleteFragmentBinding.bind(view)
|
||||
|
||||
val exportSuccessCount = args.exportMessageCount - args.exportMessageFailureCount
|
||||
binding.exportCompleteStatus.text = resources.getQuantityString(R.plurals.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, exportSuccessCount, args.exportMessageCount)
|
||||
binding.retryButton.setOnClickListener { findNavController().safeNavigate(SmsExportDirections.actionDirectToExportYourSmsMessagesFragment()) }
|
||||
binding.continueButton.setOnClickListener { findNavController().safeNavigate(SmsExportDirections.actionDirectToChooseANewDefaultSmsAppFragment()) }
|
||||
binding.bullet3Text.apply {
|
||||
setLinkColor(ContextCompat.getColor(requireContext(), R.color.signal_colorPrimary))
|
||||
setLearnMoreVisible(true, R.string.ExportSmsPartiallyComplete__contact_us)
|
||||
setOnLinkClickListener {
|
||||
findNavController().safeNavigate(SmsExportDirections.actionDirectToHelpFragment())
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTask.runWhenValid(
|
||||
viewLifecycleOwner.lifecycle,
|
||||
{ SignalDatabase.messages.getUnexportedInsecureMessagesEstimatedSize() + SignalDatabase.messages.getUnexportedInsecureMessagesEstimatedSize() },
|
||||
{ totalSize ->
|
||||
binding.bullet1Text.setText(getString(R.string.ExportSmsPartiallyComplete__ensure_you_have_an_additional_s_free_on_your_phone_to_export_your_messages, Formatter.formatFileSize(requireContext(), totalSize)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import org.signal.smsexporter.DefaultSmsHelper
|
||||
import org.signal.smsexporter.SmsExportProgress
|
||||
import org.signal.smsexporter.SmsExportService
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.ExportYourSmsMessagesFragmentBinding
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* "Welcome" screen for exporting sms
|
||||
*/
|
||||
class ExportYourSmsMessagesFragment : Fragment(R.layout.export_your_sms_messages_fragment) {
|
||||
|
||||
private var navigationDisposable = Disposable.disposed()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = ExportYourSmsMessagesFragmentBinding.bind(view)
|
||||
|
||||
binding.toolbar.setOnClickListener {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
binding.continueButton.setOnClickListener {
|
||||
if (DefaultSmsHelper.isDefaultSms(requireContext())) {
|
||||
findNavController().safeNavigate(ExportYourSmsMessagesFragmentDirections.actionExportYourSmsMessagesFragmentToExportingSmsMessagesFragment())
|
||||
} else {
|
||||
findNavController().safeNavigate(ExportYourSmsMessagesFragmentDirections.actionExportYourSmsMessagesFragmentToSetSignalAsDefaultSmsAppFragment())
|
||||
}
|
||||
}
|
||||
|
||||
Material3OnScrollHelper(requireActivity(), binding.toolbar, viewLifecycleOwner).attach(binding.scrollView)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
navigationDisposable = SmsExportService
|
||||
.progressState
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
if (it !is SmsExportProgress.Init) {
|
||||
findNavController().safeNavigate(ExportYourSmsMessagesFragmentDirections.actionExportYourSmsMessagesFragmentToExportingSmsMessagesFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
navigationDisposable.dispose()
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.smsexporter.SmsExportProgress
|
||||
import org.signal.smsexporter.SmsExportService
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.ExportingSmsMessagesFragmentBinding
|
||||
import org.thoughtcrime.securesms.exporter.SignalSmsExportService
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.mb
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* "Export in progress" fragment which should be displayed
|
||||
* when we start exporting messages.
|
||||
*/
|
||||
class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fragment) {
|
||||
|
||||
private val viewModel: SmsExportViewModel by activityViewModels()
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private var navigationDisposable = Disposable.disposed()
|
||||
|
||||
override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
|
||||
val inflater = super.onGetLayoutInflater(savedInstanceState)
|
||||
val contextThemeWrapper: Context = ContextThemeWrapper(requireContext(), R.style.Signal_DayNight)
|
||||
return inflater.cloneInContext(contextThemeWrapper)
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
navigationDisposable = SmsExportService
|
||||
.progressState
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { smsExportProgress ->
|
||||
if (smsExportProgress is SmsExportProgress.Done) {
|
||||
SmsExportService.clearProgressState()
|
||||
if (smsExportProgress.errorCount == 0) {
|
||||
findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsCompleteFragment(smsExportProgress.total, smsExportProgress.errorCount))
|
||||
} else if (smsExportProgress.errorCount == smsExportProgress.total) {
|
||||
findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsFullErrorFragment(smsExportProgress.total, smsExportProgress.errorCount))
|
||||
} else {
|
||||
findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsPartiallyCompleteFragment(smsExportProgress.total, smsExportProgress.errorCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
navigationDisposable.dispose()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = ExportingSmsMessagesFragmentBinding.bind(view)
|
||||
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
lifecycleDisposable += SmsExportService.progressState.observeOn(AndroidSchedulers.mainThread()).subscribe {
|
||||
when (it) {
|
||||
SmsExportProgress.Init -> binding.progress.isIndeterminate = true
|
||||
SmsExportProgress.Starting -> binding.progress.isIndeterminate = true
|
||||
is SmsExportProgress.InProgress -> {
|
||||
binding.progress.isIndeterminate = false
|
||||
binding.progress.max = it.total
|
||||
binding.progress.progress = it.progress
|
||||
binding.progressLabel.text = resources.getQuantityString(R.plurals.ExportingSmsMessagesFragment__exporting_d_of_d, it.total, it.progress, it.total)
|
||||
}
|
||||
is SmsExportProgress.Done -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleDisposable += ExportingSmsRepository()
|
||||
.getSmsExportSizeEstimations()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { (internalFreeSpace, estimatedRequiredSpace) ->
|
||||
val adjustedFreeSpace = internalFreeSpace - estimatedRequiredSpace - 100.mb
|
||||
if (estimatedRequiredSpace > adjustedFreeSpace) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.ExportingSmsMessagesFragment__you_may_not_have_enough_disk_space)
|
||||
.setMessage(getString(R.string.ExportingSmsMessagesFragment__you_need_approximately_s_to_export_your_messages_ensure_you_have_enough_space_before_continuing, Formatter.formatFileSize(requireContext(), estimatedRequiredSpace)))
|
||||
.setPositiveButton(R.string.ExportingSmsMessagesFragment__continue_anyway) { _, _ -> checkPermissionsAndStartExport() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionDirectToExportYourSmsMessagesFragment()) }
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
} else {
|
||||
checkPermissionsAndStartExport()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun checkPermissionsAndStartExport() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.READ_SMS)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.ExportingSmsMessagesFragment__signal_needs_the_sms_permission_to_be_able_to_export_your_sms_messages), R.drawable.ic_messages_solid_24)
|
||||
.onAllGranted { SignalSmsExportService.start(requireContext(), viewModel.isReExport) }
|
||||
.withPermanentDenialDialog(getString(R.string.ExportingSmsMessagesFragment__signal_needs_the_sms_permission_to_be_able_to_export_your_sms_messages)) { requireActivity().finish() }
|
||||
.onAnyDenied { checkPermissionsAndStartExport() }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.storage.StorageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import java.io.File
|
||||
|
||||
class ExportingSmsRepository(private val context: Application = ApplicationDependencies.getApplication()) {
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
fun getSmsExportSizeEstimations(): Single<SmsExportSizeEstimations> {
|
||||
return Single.fromCallable {
|
||||
val internalStorageFile = if (Build.VERSION.SDK_INT < 24) {
|
||||
File(context.applicationInfo.dataDir)
|
||||
} else {
|
||||
context.dataDir
|
||||
}
|
||||
|
||||
val internalFreeSpace: Long = if (Build.VERSION.SDK_INT < 26) {
|
||||
internalStorageFile.usableSpace
|
||||
} else {
|
||||
val storageManagerFreeSpace = ContextCompat.getSystemService(context, StorageManager::class.java)?.let { storageManager ->
|
||||
storageManager.getAllocatableBytes(storageManager.getUuidForPath(internalStorageFile))
|
||||
}
|
||||
storageManagerFreeSpace ?: internalStorageFile.usableSpace
|
||||
}
|
||||
|
||||
SmsExportSizeEstimations(internalFreeSpace, SignalDatabase.messages.getUnexportedInsecureMessagesEstimatedSize() + SignalDatabase.messages.getUnexportedInsecureMessagesEstimatedSize())
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
data class SmsExportSizeEstimations(val estimatedInternalFreeSpace: Long, val estimatedRequiredSpace: Long)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.signal.smsexporter.BecomeSmsAppFailure
|
||||
import org.signal.smsexporter.DefaultSmsHelper
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.SetSignalAsDefaultSmsAppFragmentBinding
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class SetSignalAsDefaultSmsAppFragment : Fragment(R.layout.set_signal_as_default_sms_app_fragment) {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = SetSignalAsDefaultSmsAppFragmentBinding.bind(view)
|
||||
|
||||
val smsDefaultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (DefaultSmsHelper.isDefaultSms(requireContext())) {
|
||||
navigateToExporter()
|
||||
}
|
||||
}
|
||||
|
||||
binding.continueButton.setOnClickListener {
|
||||
DefaultSmsHelper.becomeDefaultSms(requireContext()).either(
|
||||
onSuccess = {
|
||||
smsDefaultLauncher.launch(it)
|
||||
},
|
||||
onFailure = {
|
||||
when (it) {
|
||||
BecomeSmsAppFailure.ALREADY_DEFAULT_SMS -> navigateToExporter()
|
||||
BecomeSmsAppFailure.ROLE_IS_NOT_AVAILABLE -> error("Should never happen")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToExporter() {
|
||||
findNavController().safeNavigate(SetSignalAsDefaultSmsAppFragmentDirections.actionSetSignalAsDefaultSmsAppFragmentToExportingSmsMessagesFragment())
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FragmentWrapperActivity
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.util.WindowUtil
|
||||
|
||||
class SmsExportActivity : FragmentWrapperActivity() {
|
||||
|
||||
private lateinit var viewModel: SmsExportViewModel
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
WindowUtil.setLightStatusBarFromTheme(this)
|
||||
NotificationManagerCompat.from(this).cancel(NotificationIds.SMS_EXPORT_COMPLETE)
|
||||
}
|
||||
|
||||
@Suppress("ReplaceGetOrSet")
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
onBackPressedDispatcher.addCallback(this, OnBackPressed())
|
||||
|
||||
val factory = SmsExportViewModel.Factory(intent.getBooleanExtra(IS_FROM_MEGAPHONE, false), intent.getBooleanExtra(IS_RE_EXPORT, false))
|
||||
viewModel = ViewModelProvider(this, factory).get(SmsExportViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun getFragment(): Fragment {
|
||||
return NavHostFragment.create(R.navigation.sms_export)
|
||||
}
|
||||
|
||||
private inner class OnBackPressed : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!findNavController(R.id.fragment_container).popBackStack()) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val IS_RE_EXPORT = "is_re_export"
|
||||
private const val IS_FROM_MEGAPHONE = "is_from_megaphone"
|
||||
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
object SmsExportDialogs {
|
||||
@JvmStatic
|
||||
fun showSmsRemovalDialog(context: Context, view: View) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.RemoveSmsMessagesDialogFragment__remove_sms_messages)
|
||||
.setMessage(R.string.RemoveSmsMessagesDialogFragment__you_can_now_remove_sms_messages_from_signal)
|
||||
.setPositiveButton(R.string.RemoveSmsMessagesDialogFragment__keep_messages) { _, _ ->
|
||||
Snackbar.make(view, R.string.SmsSettingsFragment__you_can_remove_sms_messages_from_signal_in_settings, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
.setNegativeButton(R.string.RemoveSmsMessagesDialogFragment__remove_messages) { _, _ ->
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
SignalDatabase.messages.deleteExportedMessages()
|
||||
SignalDatabase.messages.deleteExportedMessages()
|
||||
}
|
||||
Snackbar.make(view, R.string.SmsSettingsFragment__removing_sms_messages_from_signal, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showSmsReExportDialog(context: Context, continueCallback: Runnable) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.ReExportSmsMessagesDialogFragment__export_sms_again)
|
||||
.setMessage(R.string.ReExportSmsMessagesDialogFragment__you_already_exported_your_sms_messages)
|
||||
.setPositiveButton(R.string.ReExportSmsMessagesDialogFragment__continue) { _, _ -> continueCallback.run() }
|
||||
.setNegativeButton(R.string.ReExportSmsMessagesDialogFragment__cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.SmsExportHelpFragmentBinding
|
||||
import org.thoughtcrime.securesms.help.HelpFragment
|
||||
|
||||
/**
|
||||
* Fragment wrapper around the app settings help fragment to provide a toolbar and set default category for sms export.
|
||||
*/
|
||||
class SmsExportHelpFragment : LoggingFragment(R.layout.sms_export_help_fragment) {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = SmsExportHelpFragmentBinding.bind(view)
|
||||
|
||||
binding.toolbar.setOnClickListener {
|
||||
if (!findNavController().popBackStack()) {
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(binding.smsExportHelpFragmentFragment.id, HelpFragment().apply { arguments = bundleOf(HelpFragment.START_CATEGORY_INDEX to HelpFragment.SMS_EXPORT_INDEX) })
|
||||
.commitNow()
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.thoughtcrime.securesms.exporter.flow
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
||||
/**
|
||||
* Hold shared state for the SMS export flow.
|
||||
*
|
||||
* Note: Will be expanded on eventually to support different behavior when entering via megaphone.
|
||||
*/
|
||||
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(isFromMegaphone, isReExport)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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.bullet1Text.text = getString(R.string.SmsRemoval_info_bullet_1)
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminderDialog;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
@@ -108,7 +107,6 @@ public final class Megaphones {
|
||||
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
|
||||
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
|
||||
put(Event.GRANT_FULL_SCREEN_INTENT, shouldShowGrantFullScreenIntentPermission(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER);
|
||||
put(Event.SMS_EXPORT, new SmsExportReminderSchedule(context));
|
||||
put(Event.BACKUP_SCHEDULE_PERMISSION, shouldShowBackupSchedulePermissionMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER);
|
||||
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION, shouldShowTurnOffCircumventionMegaphone() ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(7)) : NEVER);
|
||||
@@ -141,8 +139,6 @@ public final class Megaphones {
|
||||
return buildRemoteMegaphone(context);
|
||||
case BACKUP_SCHEDULE_PERMISSION:
|
||||
return buildBackupPermissionMegaphone(context);
|
||||
case SMS_EXPORT:
|
||||
return buildSmsExportMegaphone(context);
|
||||
case SET_UP_YOUR_USERNAME:
|
||||
return buildSetUpYourUsernameMegaphone(context);
|
||||
case GRANT_FULL_SCREEN_INTENT:
|
||||
@@ -321,24 +317,6 @@ public final class Megaphones {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull Megaphone buildSmsExportMegaphone(@NonNull Context context) {
|
||||
SmsExportPhase phase = SignalStore.misc().getSmsExportPhase();
|
||||
|
||||
Megaphone.Builder builder = new Megaphone.Builder(Event.SMS_EXPORT, Megaphone.Style.FULLSCREEN)
|
||||
.setOnVisibleListener((megaphone, controller) -> {
|
||||
if (phase.isBlockingUi()) {
|
||||
SmsExportReminderSchedule.setShowPhase3Megaphone(false);
|
||||
}
|
||||
controller.onMegaphoneNavigationRequested(new Intent(context, SmsExportMegaphoneActivity.class), SmsExportMegaphoneActivity.REQUEST_CODE);
|
||||
});
|
||||
|
||||
if (phase.isBlockingUi()) {
|
||||
builder.disableSnooze();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static @NonNull Megaphone buildSetUpYourUsernameMegaphone(@NonNull Context context) {
|
||||
return new Megaphone.Builder(Event.SET_UP_YOUR_USERNAME, Megaphone.Style.BASIC)
|
||||
.setTitle(R.string.NewWaysToConnectDialogFragment__new_ways_to_connect)
|
||||
@@ -471,7 +449,6 @@ public final class Megaphones {
|
||||
TURN_OFF_CENSORSHIP_CIRCUMVENTION("turn_off_censorship_circumvention"),
|
||||
REMOTE_MEGAPHONE("remote_megaphone"),
|
||||
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission"),
|
||||
SMS_EXPORT("sms_export"),
|
||||
SET_UP_YOUR_USERNAME("set_up_your_username"),
|
||||
GRANT_FULL_SCREEN_INTENT("grant_full_screen_intent");
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package org.thoughtcrime.securesms.megaphone
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
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.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() {
|
||||
|
||||
companion object {
|
||||
const val REQUEST_CODE: Short = 5343
|
||||
}
|
||||
|
||||
private val theme: DynamicTheme = DynamicNoActionBarTheme()
|
||||
private lateinit var binding: SmsRemovalInformationFragmentBinding
|
||||
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onPreCreate() {
|
||||
theme.onCreate(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
binding = SmsRemovalInformationFragmentBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.SMS_EXPORT)
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
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.laterButton.visible = false
|
||||
binding.bullet1Text.setText(R.string.SmsRemoval_info_bullet_1_phase_3)
|
||||
} else {
|
||||
binding.bullet1Text.text = getString(R.string.SmsRemoval_info_bullet_1)
|
||||
|
||||
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_will_no_longer_support_sms)
|
||||
binding.laterButton.setOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
binding.exportSmsButton.setOnClickListener {
|
||||
smsExportLauncher.launch(SmsExportActivity.createIntent(this))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.SMS_EXPORT)
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
theme.onResume(this)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.thoughtcrime.securesms.megaphone
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
|
||||
class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedule {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
var showPhase3Megaphone = true
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun shouldDisplay(seenCount: Int, lastSeen: Long, firstVisible: Long, currentTime: Long): Boolean {
|
||||
return if (Util.isDefaultSmsProvider(context)) {
|
||||
showPhase3Megaphone
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user