mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 18:30:20 +01:00
Add re-export SMS support and hard code Phase 0.
This commit is contained in:
committed by
Alex Hart
parent
fd1d2ec8fc
commit
7c60c32918
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user