Add re-export SMS support and hard code Phase 0.

main
Cody Henthorne 2022-11-16 11:42:41 -05:00 zatwierdzone przez Alex Hart
rodzic fd1d2ec8fc
commit 7c60c32918
13 zmienionych plików z 129 dodań i 55 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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.
*/

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)
}
}
}
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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)))
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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
}
}

Wyświetl plik

@ -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
*

Wyświetl plik

@ -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>

Wyświetl plik

@ -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
/**