Add re-export SMS support and hard code Phase 0.
This commit is contained in:
parent
fd1d2ec8fc
commit
7c60c32918
13 changed files with 129 additions and 55 deletions
|
@ -70,6 +70,16 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
|||
}
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -98,6 +98,16 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
|||
}
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -417,6 +417,20 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the exported state and exported flag so messages can be re-exported.
|
||||
*/
|
||||
public void clearExportState() {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.putNull(EXPORT_STATE);
|
||||
values.put(EXPORTED, MessageExportStatus.UNEXPORTED.serialize());
|
||||
|
||||
SQLiteDatabaseExtensionsKt.update(getWritableDatabase(), getTableName())
|
||||
.values(values)
|
||||
.where(EXPORT_STATE + " IS NOT NULL OR " + EXPORTED + " != ?", MessageExportStatus.UNEXPORTED)
|
||||
.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the exported status (not state) to the default for clearing errors.
|
||||
*/
|
||||
|
|
|
@ -31,8 +31,10 @@ class SignalSmsExportService : SmsExportService() {
|
|||
/**
|
||||
* Launches the export service and immediately begins exporting messages.
|
||||
*/
|
||||
fun start(context: Context) {
|
||||
ContextCompat.startForegroundService(context, Intent(context, SignalSmsExportService::class.java))
|
||||
fun start(context: Context, clearPreviousExportState: Boolean) {
|
||||
val intent = Intent(context, SignalSmsExportService::class.java)
|
||||
.apply { putExtra(CLEAR_PREVIOUS_EXPORT_STATE_EXTRA, clearPreviousExportState) }
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +82,11 @@ class SignalSmsExportService : SmsExportService() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun clearPreviousExportState() {
|
||||
SignalDatabase.sms.clearExportState()
|
||||
SignalDatabase.mms.clearExportState()
|
||||
}
|
||||
|
||||
override fun prepareForExport() {
|
||||
SignalDatabase.sms.clearInsecureMessageExportedErrorStatus()
|
||||
SignalDatabase.mms.clearInsecureMessageExportedErrorStatus()
|
||||
|
|
|
@ -8,6 +8,7 @@ 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
|
||||
|
@ -28,6 +29,8 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
|||
*/
|
||||
class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fragment) {
|
||||
|
||||
private val viewModel: SmsExportViewModel by activityViewModels()
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private var navigationDisposable = Disposable.disposed()
|
||||
|
||||
|
@ -109,7 +112,7 @@ class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fr
|
|||
.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()) }
|
||||
.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()
|
||||
|
|
|
@ -6,6 +6,7 @@ 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
|
||||
|
@ -15,15 +16,21 @@ 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_RE_EXPORT, false))
|
||||
viewModel = ViewModelProvider(this, factory).get(SmsExportViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun getFragment(): Fragment {
|
||||
|
@ -39,7 +46,14 @@ class SmsExportActivity : FragmentWrapperActivity() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val IS_RE_EXPORT = "is_re_export"
|
||||
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
fun createIntent(context: Context): Intent = Intent(context, SmsExportActivity::class.java)
|
||||
fun createIntent(context: Context, isReExport: Boolean = false): Intent {
|
||||
return Intent(context, SmsExportActivity::class.java).apply {
|
||||
putExtra(IS_RE_EXPORT, isReExport)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,14 @@ object SmsExportDialogs {
|
|||
}
|
||||
.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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
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 isReExport: Boolean) : ViewModel() {
|
||||
class Factory(private val isReExport: Boolean) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(SmsExportViewModel(isReExport)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
|
|||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -28,8 +29,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
|||
private static final String LAST_FCM_FOREGROUND_TIME = "misc.last_fcm_foreground_time";
|
||||
private static final String LAST_FOREGROUND_TIME = "misc.last_foreground_time";
|
||||
private static final String PNI_INITIALIZED_DEVICES = "misc.pni_initialized_devices";
|
||||
private static final String SMS_PHASE_1_START_MS = "misc.sms_export.phase_1_start.2";
|
||||
private static final String STORIES_FEATURE_AVAILABLE_MS = "misc.stories_feature_available_ms";
|
||||
private static final String SMS_PHASE_1_START_MS = "misc.sms_export.phase_1_start.3";
|
||||
|
||||
MiscellaneousValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -42,10 +42,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
|||
|
||||
@Override
|
||||
@NonNull List<String> getKeysToIncludeInBackup() {
|
||||
return Arrays.asList(
|
||||
SMS_PHASE_1_START_MS,
|
||||
STORIES_FEATURE_AVAILABLE_MS
|
||||
);
|
||||
return Collections.singletonList(SMS_PHASE_1_START_MS);
|
||||
}
|
||||
|
||||
public long getLastPrekeyRefreshTime() {
|
||||
|
@ -234,20 +231,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
|||
}
|
||||
}
|
||||
|
||||
public long getStoriesFeatureAvailableTimestamp() {
|
||||
return getLong(STORIES_FEATURE_AVAILABLE_MS, 0);
|
||||
}
|
||||
|
||||
public void setStoriesFeatureAvailableTimestamp(long timestamp) {
|
||||
putLong(STORIES_FEATURE_AVAILABLE_MS, timestamp);
|
||||
}
|
||||
|
||||
public @NonNull SmsExportPhase getSmsExportPhase() {
|
||||
if (getLong(SMS_PHASE_1_START_MS, 0) == 0) {
|
||||
return SmsExportPhase.PHASE_0;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
return SmsExportPhase.getCurrentPhase(now - getLong(SMS_PHASE_1_START_MS, now));
|
||||
return SmsExportPhase.PHASE_0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ 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.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedule {
|
||||
|
@ -32,17 +30,8 @@ class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedul
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
@WorkerThread
|
||||
fun shouldShowMegaphone(): Boolean {
|
||||
return if (SignalStore.misc().storiesFeatureAvailableTimestamp == 0L) {
|
||||
SignalStore.misc().storiesFeatureAvailableTimestamp = System.currentTimeMillis()
|
||||
false
|
||||
} else if (System.currentTimeMillis() > (SignalStore.misc().storiesFeatureAvailableTimestamp + FeatureFlags.smsExportMegaphoneDelayDays().days.inWholeMilliseconds)) {
|
||||
SignalStore.misc().startSmsPhase1()
|
||||
FeatureFlags.smsExporter() && Util.isDefaultSmsProvider(context)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
private fun shouldShowMegaphone(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,6 @@ public final class FeatureFlags {
|
|||
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";
|
||||
private static final String SMS_EXPORT_MEGAPHONE_DELAY_DAYS = "android.smsExport.megaphoneDelayDays.2";
|
||||
public static final String CREDIT_CARD_PAYMENTS = "android.credit.card.payments.3";
|
||||
private static final String PAYMENTS_REQUEST_ACTIVATE_FLOW = "android.payments.requestActivateFlow";
|
||||
private static final String KEEP_MUTED_CHATS_ARCHIVED = "android.keepMutedChatsArchived";
|
||||
|
@ -160,7 +159,6 @@ public final class FeatureFlags {
|
|||
RECIPIENT_MERGE_V2,
|
||||
SMS_EXPORTER,
|
||||
HIDE_CONTACTS,
|
||||
SMS_EXPORT_MEGAPHONE_DELAY_DAYS,
|
||||
CREDIT_CARD_PAYMENTS,
|
||||
PAYMENTS_REQUEST_ACTIVATE_FLOW,
|
||||
KEEP_MUTED_CHATS_ARCHIVED,
|
||||
|
@ -231,7 +229,6 @@ public final class FeatureFlags {
|
|||
TELECOM_MODEL_BLOCKLIST,
|
||||
CAMERAX_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2,
|
||||
SMS_EXPORT_MEGAPHONE_DELAY_DAYS,
|
||||
CREDIT_CARD_PAYMENTS,
|
||||
PAYMENTS_REQUEST_ACTIVATE_FLOW,
|
||||
KEEP_MUTED_CHATS_ARCHIVED,
|
||||
|
@ -549,13 +546,6 @@ public final class FeatureFlags {
|
|||
return getBoolean(HIDE_CONTACTS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of days to postpone the sms export megaphone and Phase 1 start.
|
||||
*/
|
||||
public static int smsExportMegaphoneDelayDays() {
|
||||
return getInteger(SMS_EXPORT_MEGAPHONE_DELAY_DAYS, 14);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should allow credit card payments for donations
|
||||
*
|
||||
|
|
|
@ -4029,6 +4029,8 @@
|
|||
<string name="SmsSettingsFragment__use_as_default_sms_app">Use as default SMS app</string>
|
||||
<!-- Preference title to export sms -->
|
||||
<string name="SmsSettingsFragment__export_sms_messages">Export SMS messages</string>
|
||||
<!-- Preference title to re-export sms -->
|
||||
<string name="SmsSettingsFragment__export_sms_messages_again">Export SMS messages again</string>
|
||||
<!-- Preference title to delete sms -->
|
||||
<string name="SmsSettingsFragment__remove_sms_messages">Remove SMS messages</string>
|
||||
<!-- Snackbar text to confirm deletion -->
|
||||
|
@ -4037,6 +4039,8 @@
|
|||
<string name="SmsSettingsFragment__you_can_remove_sms_messages_from_signal_in_settings">You can remove SMS messages from Signal in Settings at any time.</string>
|
||||
<!-- Description for export sms preference -->
|
||||
<string name="SmsSettingsFragment__you_can_export_your_sms_messages_to_your_phones_sms_database">You can export your SMS messages to your phone\'s SMS database</string>
|
||||
<!-- Description for re-export sms preference -->
|
||||
<string name="SmsSettingsFragment__exporting_again_can_result_in_duplicate_messages">Exporting again can result in duplicate messages.</string>
|
||||
<!-- Description for remove sms preference -->
|
||||
<string name="SmsSettingsFragment__remove_sms_messages_from_signal_to_clear_up_storage_space">Remove SMS messages from Signal to clear up storage space.</string>
|
||||
<!-- Information message shown at the top of sms settings to indicate it is being removed soon. -->
|
||||
|
@ -5442,6 +5446,16 @@
|
|||
<!-- Message of dialog -->
|
||||
<string name="RemoveSmsMessagesDialogFragment__you_can_now_remove_sms_messages_from_signal">You can now remove SMS messages from Signal to clear up storage space. They will still be available to other SMS apps on your phone even if you remove them.</string>
|
||||
|
||||
<!-- ReExportSmsMessagesDialogFragment -->
|
||||
<!-- Action button to re-export messages -->
|
||||
<string name="ReExportSmsMessagesDialogFragment__continue">Continue</string>
|
||||
<!-- Action button to cancel re-export process -->
|
||||
<string name="ReExportSmsMessagesDialogFragment__cancel">Cancel</string>
|
||||
<!-- Title of dialog -->
|
||||
<string name="ReExportSmsMessagesDialogFragment__export_sms_again">Export SMS again?</string>
|
||||
<!-- Message of dialog -->
|
||||
<string name="ReExportSmsMessagesDialogFragment__you_already_exported_your_sms_messages">You already exported your SMS messages.\nWARNING: If you continue, you may end up with duplicate messages.</string>
|
||||
|
||||
<!-- SetSignalAsDefaultSmsAppFragment -->
|
||||
<!-- Title of the screen -->
|
||||
<string name="SetSignalAsDefaultSmsAppFragment__set_signal_as_the_default_sms_app">Set Signal as the default SMS app</string>
|
||||
|
|
|
@ -25,16 +25,17 @@ import java.util.concurrent.Executors
|
|||
abstract class SmsExportService : Service() {
|
||||
|
||||
companion object {
|
||||
fun clearProgressState() {
|
||||
progressState.onNext(SmsExportProgress.Init)
|
||||
}
|
||||
|
||||
private val TAG = Log.tag(SmsExportService::class.java)
|
||||
const val CLEAR_PREVIOUS_EXPORT_STATE_EXTRA = "clear_previous_export_state"
|
||||
|
||||
/**
|
||||
* Progress state which can be listened to by interested components, such as fragments.
|
||||
*/
|
||||
val progressState: BehaviorProcessor<SmsExportProgress> = BehaviorProcessor.createDefault(SmsExportProgress.Init)
|
||||
|
||||
fun clearProgressState() {
|
||||
progressState.onNext(SmsExportProgress.Init)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
|
@ -47,18 +48,18 @@ abstract class SmsExportService : Service() {
|
|||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.d(TAG, "Got start command in SMS Export Service")
|
||||
|
||||
startExport()
|
||||
startExport(intent?.getBooleanExtra(CLEAR_PREVIOUS_EXPORT_STATE_EXTRA, false) ?: false)
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun startExport() {
|
||||
private fun startExport(clearExportState: Boolean) {
|
||||
if (isStarted) {
|
||||
Log.d(TAG, "Already running exporter.")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Running export...")
|
||||
Log.d(TAG, "Running export clearExportState: $clearExportState")
|
||||
|
||||
isStarted = true
|
||||
updateNotification(-1, -1)
|
||||
|
@ -67,6 +68,10 @@ abstract class SmsExportService : Service() {
|
|||
var progress = 0
|
||||
var errorCount = 0
|
||||
executor.execute {
|
||||
if (clearExportState) {
|
||||
clearPreviousExportState()
|
||||
}
|
||||
|
||||
prepareForExport()
|
||||
val totalCount = getUnexportedMessageCount()
|
||||
getUnexportedMessages().forEach { message ->
|
||||
|
@ -124,7 +129,14 @@ abstract class SmsExportService : Service() {
|
|||
*/
|
||||
protected abstract fun getExportCompleteNotification(): ExportNotification?
|
||||
|
||||
/** Called prior to starting export for any task setup that may need to occur. */
|
||||
/**
|
||||
* Called prior to starting export if the user has requested previous export state to be cleared.
|
||||
*/
|
||||
protected open fun clearPreviousExportState() = Unit
|
||||
|
||||
/**
|
||||
* Called prior to starting export for any task setup that may need to occur.
|
||||
*/
|
||||
protected open fun prepareForExport() = Unit
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue