Add phased SMS removal UX.

fork-5.53.8
Cody Henthorne 2022-10-13 11:33:13 -04:00 zatwierdzone przez Greyson Parrelli
rodzic 8a238a66e7
commit b6db7e7af6
68 zmienionych plików z 1214 dodań i 187 usunięć

Wyświetl plik

@ -666,6 +666,12 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
<activity android:name=".ratelimit.RecaptchaProofActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
@ -685,6 +691,7 @@
<activity android:name=".exporter.flow.SmsExportActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

Wyświetl plik

@ -103,6 +103,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onDonateClicked();
void onBlockJoinRequest(@NonNull Recipient recipient);
void onRecipientNameClicked(@NonNull RecipientId target);
void onInviteToSignalClicked();
/** @return true if handled, false if you want to let the normal url handling continue */
boolean onUrlClicked(@NonNull String url);

Wyświetl plik

@ -28,6 +28,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
@ -69,8 +70,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@Override
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int displayMode = Util.isDefaultSmsProvider(this) ? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
boolean includeSms = Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported();
int displayMode = includeSms ? DisplayMode.FLAG_ALL : DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
}

Wyświetl plik

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
@ -118,7 +119,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
if (Util.isDefaultSmsProvider(this)) {
if (Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
} else {

Wyświetl plik

@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import java.io.IOException;
@ -104,12 +105,14 @@ public class NewConversationActivity extends ContactSelectionActivity
@Override
public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
boolean smsSupported = Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported();
if (recipientId.isPresent()) {
launch(Recipient.resolved(recipientId.get()));
} else {
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
if (SignalStore.account().isRegistered() && NetworkConstraint.isMet(getApplication())) {
if (SignalStore.account().isRegistered()) {
Log.i(TAG, "[onContactSelected] Doing contact refresh.");
AlertDialog progress = SimpleProgressDialog.show(this);
@ -124,15 +127,31 @@ public class NewConversationActivity extends ContactSelectionActivity
resolved = Recipient.resolved(resolved.getId());
} catch (IOException e) {
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
return null;
}
}
return resolved;
}, resolved -> {
progress.dismiss();
launch(resolved);
if (resolved != null) {
if (smsSupported || resolved.isRegistered() && resolved.hasServiceId()) {
launch(resolved);
} else {
new MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, resolved.getDisplayName(this)))
.setPositiveButton(android.R.string.ok, null)
.show();
}
} else {
new MaterialAlertDialogBuilder(this)
.setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again)
.setPositiveButton(android.R.string.ok, null)
.show();
}
});
} else {
} else if (smsSupported) {
launch(Recipient.external(this, number));
}
}
@ -260,16 +279,20 @@ public class NewConversationActivity extends ContactSelectionActivity
return null;
}
return new ActionItem(
R.drawable.ic_phone_right_24,
getString(R.string.NewConversationActivity__audio_call),
R.color.signal_colorOnSurface,
() -> CommunicationActions.startVoiceCall(this, recipient)
);
if (recipient.isRegistered() || (Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported())) {
return new ActionItem(
R.drawable.ic_phone_right_24,
getString(R.string.NewConversationActivity__audio_call),
R.color.signal_colorOnSurface,
() -> CommunicationActions.startVoiceCall(this, recipient)
);
} else {
return null;
}
}
private @Nullable ActionItem createVideoCallActionItem(@NonNull Recipient recipient) {
if (recipient.isSelf() || recipient.isMmsGroup()) {
if (recipient.isSelf() || recipient.isMmsGroup() || !recipient.isRegistered()) {
return null;
}

Wyświetl plik

@ -6,10 +6,14 @@ import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatImageButton
import com.google.android.material.snackbar.Snackbar
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.ViewUtil
import java.lang.AssertionError
import java.util.concurrent.CopyOnWriteArrayList
@ -30,6 +34,8 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
private var activeMessageSendType: MessageSendType? = null
private var defaultTransportType: MessageSendType.TransportType = MessageSendType.TransportType.SMS
private var defaultSubscriptionId: Int? = null
lateinit var snackbarContainer: View
private var popupContainer: ViewGroup? = null
init {
@ -146,10 +152,19 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
}
override fun onLongClick(v: View): Boolean {
if (!isEnabled || availableSendTypes.size == 1) {
if (!isEnabled) {
return false
}
if (availableSendTypes.size == 1) {
return if (!Util.isDefaultSmsProvider(context) || !SignalStore.misc().smsExportPhase.isSmsSupported()) {
Snackbar.make(snackbarContainer, R.string.InputPanel__sms_messaging_is_no_longer_supported_in_signal, Snackbar.LENGTH_SHORT).show()
true
} else {
false
}
}
val currentlySelected: MessageSendType = selectedSendType
val items = availableSendTypes

Wyświetl plik

@ -1,28 +1,41 @@
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()
viewModel.refresh()
}
@Suppress("ReplaceGetOrSet")
override fun bindAdapter(adapter: MappingAdapter) {
val repository = ChatsSettingsRepository()
val factory = ChatsSettingsViewModel.Factory(repository)
viewModel = ViewModelProvider(this, factory)[ChatsSettingsViewModel::class.java]
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) {
adapter.submitList(getConfiguration(it).toMappingModelList())
@ -32,14 +45,46 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
private fun getConfiguration(state: ChatsSettingsState): DSLConfiguration {
return configure {
clickPref(
title = DSLSettingsText.from(R.string.preferences__sms_mms),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_chatsSettingsFragment_to_smsSettingsFragment)
}
)
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()
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())
}
)
dividerPref()
}
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
SmsExportState.NOT_AVAILABLE -> Unit
}
}
if (state.useAsDefaultSmsApp) {
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),

Wyświetl plik

@ -1,9 +1,13 @@
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 useSystemEmoji: Boolean,
val enterKeySends: Boolean,
val chatBackupsEnabled: Boolean
val chatBackupsEnabled: Boolean,
val useAsDefaultSmsApp: Boolean,
val smsExportState: SmsExportState = SmsExportState.FETCHING
)

Wyświetl plik

@ -2,17 +2,24 @@ package org.thoughtcrime.securesms.components.settings.app.chats
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
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(private val repository: ChatsSettingsRepository) : ViewModel() {
class ChatsSettingsViewModel @JvmOverloads constructor(
private val repository: ChatsSettingsRepository = ChatsSettingsRepository(),
smsSettingsRepository: SmsSettingsRepository = SmsSettingsRepository()
) : ViewModel() {
private val refreshDebouncer = ThrottledDebouncer(500L)
private val disposables = CompositeDisposable()
private val store: Store<ChatsSettingsState> = Store(
ChatsSettingsState(
@ -20,12 +27,23 @@ class ChatsSettingsViewModel(private val repository: ChatsSettingsRepository) :
useAddressBook = SignalStore.settings().isPreferSystemContactPhotos,
useSystemEmoji = SignalStore.settings().isPreferSystemEmoji,
enterKeySends = SignalStore.settings().isEnterKeySends,
chatBackupsEnabled = SignalStore.settings().isBackupEnabled && BackupUtil.canUserAccessBackupDirectory(ApplicationDependencies.getApplication())
chatBackupsEnabled = SignalStore.settings().isBackupEnabled && BackupUtil.canUserAccessBackupDirectory(ApplicationDependencies.getApplication()),
useAsDefaultSmsApp = Util.isDefaultSmsProvider(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
@ -55,10 +73,4 @@ class ChatsSettingsViewModel(private val repository: ChatsSettingsRepository) :
store.update { it.copy(chatBackupsEnabled = backupsEnabled) }
}
}
class Factory(private val repository: ChatsSettingsRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(ChatsSettingsViewModel(repository)))
}
}
}

Wyświetl plik

@ -0,0 +1,9 @@
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
}

Wyświetl plik

@ -9,18 +9,16 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.signal.core.util.concurrent.SignalExecutors
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.database.SignalDatabase
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.SmsUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@ -38,9 +36,11 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
}
override fun bindAdapter(adapter: MappingAdapter) {
OutlinedLearnMore.register(adapter)
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
showSmsRemovalDialog()
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
}
}
@ -52,16 +52,32 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(requireContext()))
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) {
SmsSettingsState.SmsExportState.FETCHING -> Unit
SmsSettingsState.SmsExportState.HAS_UNEXPORTED_MESSAGES -> {
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()))
}
@ -69,32 +85,31 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
dividerPref()
}
SmsSettingsState.SmsExportState.ALL_MESSAGES_EXPORTED -> {
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 = {
showSmsRemovalDialog()
SmsExportDialogs.showSmsRemovalDialog(requireContext(), requireView())
}
)
dividerPref()
}
SmsSettingsState.SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
SmsSettingsState.SmsExportState.NOT_AVAILABLE -> Unit
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
SmsExportState.NOT_AVAILABLE -> Unit
}
@Suppress("DEPRECATION")
clickPref(
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
summary = DSLSettingsText.from(if (state.useAsDefaultSmsApp) R.string.arrays__enabled else R.string.arrays__disabled),
onClick = {
if (state.useAsDefaultSmsApp) {
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()
} else {
startActivityForResult(SmsUtil.getSmsRoleIntent(requireContext()), SMS_REQUEST_CODE.toInt())
}
}
)
)
}
switchPref(
title = DSLSettingsText.from(R.string.preferences__sms_delivery_reports),
@ -137,21 +152,4 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
startActivityForResult(intent, SMS_REQUEST_CODE.toInt())
}
private fun showSmsRemovalDialog() {
MaterialAlertDialogBuilder(requireContext())
.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(requireView(), 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.sms.deleteExportedMessages()
SignalDatabase.mms.deleteExportedMessages()
}
Snackbar.make(requireView(), R.string.SmsSettingsFragment__removing_sms_messages_from_signal, Snackbar.LENGTH_SHORT).show()
}
.show()
}
}

Wyświetl plik

@ -11,9 +11,9 @@ class SmsSettingsRepository(
private val smsDatabase: MessageDatabase = SignalDatabase.sms,
private val mmsDatabase: MessageDatabase = SignalDatabase.mms
) {
fun getSmsExportState(): Single<SmsSettingsState.SmsExportState> {
fun getSmsExportState(): Single<SmsExportState> {
if (!FeatureFlags.smsExporter()) {
return Single.just(SmsSettingsState.SmsExportState.NOT_AVAILABLE)
return Single.just(SmsExportState.NOT_AVAILABLE)
}
return Single.fromCallable {
@ -22,24 +22,24 @@ class SmsSettingsRepository(
}
@WorkerThread
private fun checkInsecureMessageCount(): SmsSettingsState.SmsExportState? {
private fun checkInsecureMessageCount(): SmsExportState? {
val totalSmsMmsCount = smsDatabase.insecureMessageCount + mmsDatabase.insecureMessageCount
return if (totalSmsMmsCount == 0) {
SmsSettingsState.SmsExportState.NO_SMS_MESSAGES_IN_DATABASE
SmsExportState.NO_SMS_MESSAGES_IN_DATABASE
} else {
null
}
}
@WorkerThread
private fun checkUnexportedInsecureMessageCount(): SmsSettingsState.SmsExportState {
private fun checkUnexportedInsecureMessageCount(): SmsExportState {
val totalUnexportedCount = smsDatabase.unexportedInsecureMessagesCount + mmsDatabase.unexportedInsecureMessagesCount
return if (totalUnexportedCount > 0) {
SmsSettingsState.SmsExportState.HAS_UNEXPORTED_MESSAGES
SmsExportState.HAS_UNEXPORTED_MESSAGES
} else {
SmsSettingsState.SmsExportState.ALL_MESSAGES_EXPORTED
SmsExportState.ALL_MESSAGES_EXPORTED
}
}
}

Wyświetl plik

@ -5,12 +5,4 @@ data class SmsSettingsState(
val smsDeliveryReportsEnabled: Boolean,
val wifiCallingCompatibilityEnabled: Boolean,
val smsExportState: SmsExportState = SmsExportState.FETCHING
) {
enum class SmsExportState {
FETCHING,
HAS_UNEXPORTED_MESSAGES,
ALL_MESSAGES_EXPORTED,
NO_SMS_MESSAGES_IN_DATABASE,
NOT_AVAILABLE
}
}
)

Wyświetl plik

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ContactFilterView
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader
import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.Util
@ -105,7 +106,7 @@ class SelectRecipientsFragment : LoggingFragment(), ContactSelectionListFragment
ContactsCursorLoader.DisplayMode.FLAG_HIDE_RECENT_HEADER or
ContactsCursorLoader.DisplayMode.FLAG_GROUPS_AFTER_CONTACTS
if (Util.isDefaultSmsProvider(requireContext())) {
if (Util.isDefaultSmsProvider(requireContext()) && SignalStore.misc().smsExportPhase.isSmsSupported()) {
mode = mode or ContactsCursorLoader.DisplayMode.FLAG_SMS
}

Wyświetl plik

@ -16,14 +16,17 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.L
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.LiveGroup
import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.SingleLiveEvent
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
import org.thoughtcrime.securesms.util.livedata.Store
import java.util.Optional
@ -138,11 +141,17 @@ sealed class ConversationSettingsViewModel(
}
store.update(liveRecipient.liveData) { recipient, state ->
val isAudioAvailable = (recipient.isRegistered || (Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()) && SignalStore.misc().smsExportPhase.isSmsSupported())) &&
!recipient.isGroup &&
!recipient.isBlocked &&
!recipient.isSelf &&
!recipient.isReleaseNotes
state.copy(
recipient = recipient,
buttonStripState = ButtonStripPreference.State(
isVideoAvailable = recipient.registered == RecipientDatabase.RegisteredState.REGISTERED && !recipient.isSelf && !recipient.isBlocked && !recipient.isReleaseNotes,
isAudioAvailable = !recipient.isGroup && !recipient.isSelf && !recipient.isBlocked && !recipient.isReleaseNotes,
isAudioAvailable = isAudioAvailable,
isAudioSecure = recipient.registered == RecipientDatabase.RegisteredState.REGISTERED,
isMuted = recipient.isMuted,
isMuteAvailable = !recipient.isSelf,

Wyświetl plik

@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.components.settings.models
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.databinding.DslOutlinedLearnMoreBinding
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
/**
* Show a informational text message in an outlined bubble.
*/
object OutlinedLearnMore {
fun register(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, DslOutlinedLearnMoreBinding::inflate))
}
class Model(
summary: DSLSettingsText,
val learnMoreUrl: String
) : PreferenceModel<Model>(summary = summary) {
override fun areContentsTheSame(newItem: Model): Boolean {
return super.areContentsTheSame(newItem) && learnMoreUrl == newItem.learnMoreUrl
}
}
private class ViewHolder(binding: DslOutlinedLearnMoreBinding) : BindingViewHolder<Model, DslOutlinedLearnMoreBinding>(binding) {
override fun bind(model: Model) {
binding.root.text = model.summary!!.resolve(context)
binding.root.setLearnMoreVisible(true)
binding.root.setLink(model.learnMoreUrl)
}
}
}

Wyświetl plik

@ -1486,6 +1486,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
void onVoiceNotePlaybackSpeedChanged(@NonNull Uri uri, float speed);
void onRegisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
void onInviteToSignal();
}
private class ConversationScrollListener extends OnScrollListener {
@ -2080,6 +2081,11 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
RecipientBottomSheetDialogFragment.create(target, recipient.get().getGroupId().orElse(null)).show(getParentFragmentManager(), "BOTTOM");
}
@Override
public void onInviteToSignalClicked() {
listener.onInviteToSignal();
}
@Override
public void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord) {
if (!MessageRecordUtil.hasGiftBadge(messageRecord)) {

Wyświetl plik

@ -189,6 +189,7 @@ import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
@ -401,7 +402,7 @@ public class ConversationParentFragment extends Fragment
private TextView charactersLeft;
private ConversationFragment fragment;
private Button unblockButton;
private Button makeDefaultSmsButton;
private Stub<View> smsExportStub;
private Button registerButton;
private InputAwareLayout container;
protected Stub<ReminderView> reminderView;
@ -925,8 +926,11 @@ public class ConversationParentFragment extends Fragment
}
if (isSingleConversation()) {
if (viewModel.isPushAvailable()) inflater.inflate(R.menu.conversation_callable_secure, menu);
else if (!recipient.get().isReleaseNotes()) inflater.inflate(R.menu.conversation_callable_insecure, menu);
if (viewModel.isPushAvailable()) {
inflater.inflate(R.menu.conversation_callable_secure, menu);
} else if (!recipient.get().isReleaseNotes() && Util.isDefaultSmsProvider(requireContext()) && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
inflater.inflate(R.menu.conversation_callable_insecure, menu);
}
} else if (isGroupConversation()) {
if (isActiveV2Group && Build.VERSION.SDK_INT > 19) {
inflater.inflate(R.menu.conversation_callable_groupv2, menu);
@ -1303,14 +1307,24 @@ public class ConversationParentFragment extends Fragment
private void handleInviteLink() {
String inviteText = getString(R.string.ConversationActivity_lets_switch_to_signal, getString(R.string.install_url));
if (viewModel.isDefaultSmsApplication()) {
if (viewModel.isDefaultSmsApplication() && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
composeText.appendInvite(inviteText);
} else {
} else if (recipient.get().hasSmsAddress()) {
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("smsto:" + recipient.get().requireSmsAddress()));
intent.putExtra("sms_body", inviteText);
intent.putExtra(Intent.EXTRA_TEXT, inviteText);
startActivity(intent);
} else {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, inviteText);
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(requireContext().getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, getString(R.string.InviteActivity_invite_to_signal)));
} else {
Toast.makeText(requireContext(), R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
}
@ -1590,11 +1604,13 @@ public class ConversationParentFragment extends Fragment
if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection() && smsEnabled) {
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
viewModel.insertSmsExportUpdateEvent(recipient.get());
} else {
if (isPushAvailable || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) {
sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL);
} else {
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
viewModel.insertSmsExportUpdateEvent(recipient.get());
}
}
@ -1997,7 +2013,7 @@ public class ConversationParentFragment extends Fragment
emojiDrawerStub = ViewUtil.findStubById(view, R.id.emoji_drawer_stub);
attachmentKeyboardStub = ViewUtil.findStubById(view, R.id.attachment_keyboard_stub);
unblockButton = view.findViewById(R.id.unblock_button);
makeDefaultSmsButton = view.findViewById(R.id.make_default_sms_button);
smsExportStub = ViewUtil.findStubById(view, R.id.sms_export_stub);
registerButton = view.findViewById(R.id.register_button);
container = view.findViewById(R.id.layout_container);
reminderView = ViewUtil.findStubById(view, R.id.reminder_stub);
@ -2028,6 +2044,7 @@ public class ConversationParentFragment extends Fragment
joinGroupCallButton = view.findViewById(R.id.conversation_group_call_join);
sendButton.setPopupContainer((ViewGroup) view);
sendButton.setSnackbarContainer(view.findViewById(R.id.fragment_content));
container.setIsBubble(isInBubble());
container.addOnKeyboardShownListener(this);
@ -2067,7 +2084,6 @@ public class ConversationParentFragment extends Fragment
titleView.setOnClickListener(v -> handleConversationSettings());
titleView.setOnLongClickListener(v -> handleDisplayQuickContact());
unblockButton.setOnClickListener(v -> handleUnblock());
makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms());
registerButton.setOnClickListener(v -> handleRegisterForSignal());
composeText.setOnKeyListener(composeKeyPressedListener);
@ -2708,17 +2724,29 @@ public class ConversationParentFragment extends Fragment
if (!conversationSecurityInfo.isPushAvailable() && isPushGroupConversation()) {
unblockButton.setVisibility(View.GONE);
inputPanel.setHideForBlockedState(true);
makeDefaultSmsButton.setVisibility(View.GONE);
smsExportStub.setVisibility(View.GONE);
registerButton.setVisibility(View.VISIBLE);
} else if (!conversationSecurityInfo.isPushAvailable() && !conversationSecurityInfo.isDefaultSmsApplication() && recipient.hasSmsAddress()) {
} else if (!conversationSecurityInfo.isPushAvailable() && !(SignalStore.misc().getSmsExportPhase().isSmsSupported() && conversationSecurityInfo.isDefaultSmsApplication()) && recipient.hasSmsAddress()) {
unblockButton.setVisibility(View.GONE);
inputPanel.setHideForBlockedState(true);
makeDefaultSmsButton.setVisibility(View.VISIBLE);
smsExportStub.setVisibility(View.VISIBLE);
registerButton.setVisibility(View.GONE);
TextView message = smsExportStub.get().findViewById(R.id.export_sms_message);
MaterialButton actionButton = smsExportStub.get().findViewById(R.id.export_sms_button);
if (conversationSecurityInfo.getHasUnexportedInsecureMessages()) {
message.setText(R.string.ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_you_can_export_your_messages_to_another_app_on_your_phone);
actionButton.setText(R.string.ConversationActivity__export_sms_messages);
actionButton.setOnClickListener(v -> startActivity(SmsExportActivity.createIntent(requireContext())));
} else {
message.setText(requireContext().getString(R.string.ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_invite_s_to_to_signal_to_keep_the_conversation_here, recipient.getDisplayName(requireContext())));
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
actionButton.setOnClickListener(v -> handleInviteLink());
}
} else if (recipient.isReleaseNotes() && !recipient.isBlocked()) {
unblockButton.setVisibility(View.GONE);
inputPanel.setHideForBlockedState(true);
makeDefaultSmsButton.setVisibility(View.GONE);
smsExportStub.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
if (recipient.isMuted()) {
@ -2735,7 +2763,7 @@ public class ConversationParentFragment extends Fragment
boolean inactivePushGroup = isPushGroupConversation() && !recipient.isActiveGroup();
inputPanel.setHideForBlockedState(inactivePushGroup);
unblockButton.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
smsExportStub.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
}
@ -3796,6 +3824,11 @@ public class ConversationParentFragment extends Fragment
voiceNoteMediaController.getVoiceNotePlaybackState().removeObserver(onPlaybackStartObserver);
}
@Override
public void onInviteToSignal() {
handleInviteLink();
}
@Override
public void onCursorChanged() {
if (!reactionDelegate.isShowing()) {

Wyświetl plik

@ -170,11 +170,16 @@ class ConversationRepository {
}
}
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
boolean hasUnexportedInsecureMessages = threadId != -1 && SignalDatabase.mmsSms().getUnexportedInsecureMessagesCount(threadId) > 0;
Log.i(TAG, "Returning registered state...");
return new ConversationSecurityInfo(recipient.getId(),
registeredState == RecipientDatabase.RegisteredState.REGISTERED && signalEnabled,
Util.isDefaultSmsProvider(context),
true);
true,
hasUnexportedInsecureMessages);
}).subscribeOn(Schedulers.io());
}
@ -193,4 +198,18 @@ class ConversationRepository {
listener.onChanged();
}).subscribeOn(Schedulers.io());
}
public void insertSmsExportUpdateEvent(Recipient recipient) {
SignalExecutors.BOUNDED.execute(() -> {
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
if (threadId == -1 || !Util.isDefaultSmsProvider(context)) {
return;
}
if (RecipientUtil.isSmsOnly(threadId, recipient) && (!recipient.isMmsGroup() || Util.isDefaultSmsProvider(context))) {
SignalDatabase.sms().insertSmsExportMessage(recipient.getId(), threadId);
}
});
}
}

Wyświetl plik

@ -6,5 +6,6 @@ data class ConversationSecurityInfo(
val recipientId: RecipientId = RecipientId.UNKNOWN,
val isPushAvailable: Boolean = false,
val isDefaultSmsApplication: Boolean = false,
val isInitialized: Boolean = false
val isInitialized: Boolean = false,
val hasUnexportedInsecureMessages: Boolean = false
)

Wyświetl plik

@ -535,6 +535,15 @@ public final class ConversationUpdateItem extends FrameLayout
});
actionButton.setText(R.string.ConversationUpdateItem_donate);
} else if (conversationMessage.getMessageRecord().isSmsExportType()) {
actionButton.setVisibility(View.VISIBLE);
actionButton.setOnClickListener(v -> {
if (batchSelected.isEmpty() && eventListener != null) {
eventListener.onInviteToSignalClicked();
}
});
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
} else {
actionButton.setVisibility(GONE);
actionButton.setOnClickListener(null);

Wyświetl plik

@ -442,6 +442,10 @@ public class ConversationViewModel extends ViewModel {
EventBus.getDefault().unregister(this);
}
public void insertSmsExportUpdateEvent(@NonNull Recipient recipient) {
conversationRepository.insertSmsExportUpdateEvent(recipient);
}
enum Event {
SHOW_RECAPTCHA
}

Wyświetl plik

@ -9,10 +9,12 @@ import androidx.annotation.StringRes
import kotlinx.parcelize.Parcelize
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CharacterCalculator
import org.thoughtcrime.securesms.util.MmsCharacterCalculator
import org.thoughtcrime.securesms.util.PushCharacterCalculator
import org.thoughtcrime.securesms.util.SmsCharacterCalculator
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat
import java.lang.IllegalArgumentException
@ -138,22 +140,24 @@ sealed class MessageSendType(
options += SignalMessageSendType
try {
val subscriptions: Collection<SubscriptionInfoCompat> = SubscriptionManagerCompat(context).activeAndReadySubscriptionInfos
if (Util.isDefaultSmsProvider(context) && SignalStore.misc().smsExportPhase.isSmsSupported()) {
try {
val subscriptions: Collection<SubscriptionInfoCompat> = SubscriptionManagerCompat(context).activeAndReadySubscriptionInfos
if (subscriptions.size < 2) {
options += if (isMedia) MmsMessageSendType() else SmsMessageSendType()
} else {
options += subscriptions.map {
if (isMedia) {
MmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
} else {
SmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
if (subscriptions.size < 2) {
options += if (isMedia) MmsMessageSendType() else SmsMessageSendType()
} else {
options += subscriptions.map {
if (isMedia) {
MmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
} else {
SmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
}
}
}
} catch (e: SecurityException) {
Log.w(TAG, "Did not have permission to get SMS subscription details!")
}
} catch (e: SecurityException) {
Log.w(TAG, "Did not have permission to get SMS subscription details!")
}
return options

Wyświetl plik

@ -115,6 +115,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
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.insights.InsightsLauncher;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@ -126,6 +127,7 @@ 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;
@ -179,6 +181,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
@ -510,11 +513,16 @@ public class ConversationListFragment extends MainFragment implements ActionMode
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode != RESULT_OK) {
return;
if (requestCode == SmsExportMegaphoneActivity.REQUEST_CODE && SignalStore.misc().getSmsExportPhase().isFullscreen()) {
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 (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN) {
if (resultCode == RESULT_OK && requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN) {
Snackbar.make(fab, R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show();
viewModel.onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL);
}

Wyświetl plik

@ -13,6 +13,7 @@ import com.google.android.mms.pdu_alt.NotificationInd;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
import org.signal.core.util.SqlUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
@ -97,7 +98,6 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
public abstract List<MessageRecord> getProfileChangeDetailsRecords(long threadId, long afterTimestamp);
public abstract Set<Long> getAllRateLimitedMessageIds();
public abstract Cursor getUnexportedInsecureMessages(int limit);
public abstract int getInsecureMessageCount();
public abstract void deleteExportedMessages();
public abstract void markExpireStarted(long messageId);
@ -177,6 +177,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
public abstract void insertNumberChangeMessages(@NonNull Recipient recipient);
public abstract void insertBoostRequestMessage(@NonNull RecipientId recipientId, long threadId);
public abstract void insertThreadMergeEvent(@NonNull RecipientId recipientId, long threadId, @NonNull ThreadMergeEvent event);
public abstract void insertSmsExportMessage(@NonNull RecipientId recipientId, long threadId);
public abstract boolean deleteMessage(long messageId);
abstract void deleteThread(long threadId);
@ -247,6 +248,20 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
return getMessageCountForRecipientsAndType(getOutgoingInsecureMessageClause());
}
public int getInsecureMessageCount() {
try (Cursor cursor = getReadableDatabase().query(getTableName(), SqlUtil.COUNT, getInsecureMessageClause(), null, null, null, null)) {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
}
return 0;
}
public boolean hasSmsExportMessage(long threadId) {
return SQLiteDatabaseExtensionsKt.exists(getReadableDatabase(), getTableName(), THREAD_ID_WHERE + " AND " + getTypeField() + " = ?", threadId, Types.SMS_EXPORT_TYPE);
}
final int getSecureMessageCountForInsights() {
return getMessageCountForRecipientsAndType(getOutgoingSecureMessageClause());
}
@ -360,16 +375,30 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
}
protected String getInsecureMessageClause() {
return getInsecureMessageClause(-1);
}
protected String getInsecureMessageClause(long threadId) {
String isSent = "(" + getTypeField() + " & " + Types.BASE_TYPE_MASK + ") = " + Types.BASE_SENT_TYPE;
String isReceived = "(" + getTypeField() + " & " + Types.BASE_TYPE_MASK + ") = " + Types.BASE_INBOX_TYPE;
String isSecure = "(" + getTypeField() + " & " + (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT) + ")";
String isNotSecure = "(" + getTypeField() + " <= " + (Types.BASE_TYPE_MASK | Types.MESSAGE_ATTRIBUTE_MASK) + ")";
return String.format(Locale.ENGLISH, "(%s OR %s) AND NOT %s AND %s", isSent, isReceived, isSecure, isNotSecure);
String whereClause = String.format(Locale.ENGLISH, "(%s OR %s) AND NOT %s AND %s", isSent, isReceived, isSecure, isNotSecure);
if (threadId != -1) {
whereClause += " AND " + THREAD_ID + " = " + threadId;
}
return whereClause;
}
public int getUnexportedInsecureMessagesCount() {
try (Cursor cursor = getWritableDatabase().query(getTableName(), SqlUtil.COUNT, getInsecureMessageClause() + " AND NOT " + EXPORTED, null, null, null, null)) {
return getUnexportedInsecureMessagesCount(-1);
}
public int getUnexportedInsecureMessagesCount(long threadId) {
try (Cursor cursor = getWritableDatabase().query(getTableName(), SqlUtil.COUNT, getInsecureMessageClause(threadId) + " AND NOT " + EXPORTED, null, null, null, null)) {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}

Wyświetl plik

@ -578,6 +578,11 @@ public class MmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
@Override
public void insertSmsExportMessage(@NonNull RecipientId recipientId, long threadId) {
throw new UnsupportedOperationException();
}
@Override
public void endTransaction(SQLiteDatabase database) {
database.endTransaction();
@ -2456,17 +2461,6 @@ public class MmsDatabase extends MessageDatabase {
);
}
@Override
public int getInsecureMessageCount() {
try (Cursor cursor = getWritableDatabase().query(TABLE_NAME, SqlUtil.COUNT, getInsecureMessageClause(), null, null, null, null)) {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
}
return 0;
}
@Override
public void deleteExportedMessages() {
beginTransaction();

Wyświetl plik

@ -82,6 +82,7 @@ public interface MmsSmsColumns {
protected static final long CHANGE_NUMBER_TYPE = 14;
protected static final long BOOST_REQUEST_TYPE = 15;
protected static final long THREAD_MERGE_TYPE = 16;
protected static final long SMS_EXPORT_TYPE = 17;
protected static final long BASE_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21;
@ -366,6 +367,10 @@ public interface MmsSmsColumns {
return type == BOOST_REQUEST_TYPE;
}
public static boolean isSmsExport(long type) {
return type == SMS_EXPORT_TYPE;
}
public static boolean isGroupV2LeaveOnly(long type) {
return (type & GROUP_V2_LEAVE_BITS) == GROUP_V2_LEAVE_BITS;
}

Wyświetl plik

@ -121,7 +121,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.PARENT_STORY_ID};
private static final String SNIPPET_QUERY = "SELECT " + MmsSmsColumns.ID + ", 0 AS " + TRANSPORT + ", " + SmsDatabase.TYPE + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + SmsDatabase.TABLE_NAME + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + SmsDatabase.TYPE + " NOT IN (" + SmsDatabase.Types.PROFILE_CHANGE_TYPE + ", " + SmsDatabase.Types.GV1_MIGRATION_TYPE + ", " + SmsDatabase.Types.CHANGE_NUMBER_TYPE + ", " + SmsDatabase.Types.BOOST_REQUEST_TYPE + ") AND " + SmsDatabase.TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + SmsDatabase.TYPE + " NOT IN (" + SmsDatabase.Types.PROFILE_CHANGE_TYPE + ", " + SmsDatabase.Types.GV1_MIGRATION_TYPE + ", " + SmsDatabase.Types.CHANGE_NUMBER_TYPE + ", " + SmsDatabase.Types.BOOST_REQUEST_TYPE + ", " + SmsDatabase.Types.SMS_EXPORT_TYPE + ") AND " + SmsDatabase.TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " " +
"UNION ALL " +
"SELECT " + MmsSmsColumns.ID + ", 1 AS " + TRANSPORT + ", " + MmsDatabase.MESSAGE_BOX + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + MmsDatabase.TABLE_NAME + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + MmsDatabase.MESSAGE_BOX + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 " +
@ -401,6 +401,17 @@ public class MmsSmsDatabase extends Database {
return count;
}
public int getUnexportedInsecureMessagesCount() {
return getUnexportedInsecureMessagesCount(-1);
}
public int getUnexportedInsecureMessagesCount(long threadId) {
int count = SignalDatabase.sms().getUnexportedInsecureMessagesCount(threadId);
count += SignalDatabase.mms().getUnexportedInsecureMessagesCount(threadId);
return count;
}
public int getIncomingMeaningfulMessageCountSince(long threadId, long afterTime) {
int count = SignalDatabase.sms().getIncomingMeaningfulMessageCountSince(threadId, afterTime);
count += SignalDatabase.mms().getIncomingMeaningfulMessageCountSince(threadId, afterTime);

Wyświetl plik

@ -34,6 +34,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
import org.signal.core.util.SqlUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.util.Pair;
@ -300,8 +301,8 @@ public class SmsDatabase extends MessageDatabase {
}
private @NonNull SqlUtil.Query buildMeaningfulMessagesQuery(long threadId) {
String query = THREAD_ID + " = ? AND (NOT " + TYPE + " & ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + ")";
return SqlUtil.buildQuery(query, threadId, IGNORABLE_TYPESMASK_WHEN_COUNTING, Types.PROFILE_CHANGE_TYPE, Types.CHANGE_NUMBER_TYPE, Types.BOOST_REQUEST_TYPE);
String query = THREAD_ID + " = ? AND (NOT " + TYPE + " & ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + ")";
return SqlUtil.buildQuery(query, threadId, IGNORABLE_TYPESMASK_WHEN_COUNTING, Types.PROFILE_CHANGE_TYPE, Types.CHANGE_NUMBER_TYPE, Types.SMS_EXPORT_TYPE, Types.BOOST_REQUEST_TYPE);
}
@Override
@ -918,17 +919,6 @@ public class SmsDatabase extends MessageDatabase {
);
}
@Override
public int getInsecureMessageCount() {
try (Cursor cursor = getWritableDatabase().query(TABLE_NAME, SqlUtil.COUNT, getInsecureMessageClause(), null, null, null, null)) {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
}
return 0;
}
@Override
public void deleteExportedMessages() {
beginTransaction();
@ -1139,6 +1129,32 @@ public class SmsDatabase extends MessageDatabase {
ApplicationDependencies.getDatabaseObserver().notifyConversationListeners(threadId);
}
@Override
public void insertSmsExportMessage(@NonNull RecipientId recipientId, long threadId) {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, recipientId.serialize());
values.put(ADDRESS_DEVICE_ID, 1);
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, System.currentTimeMillis());
values.put(READ, 1);
values.put(TYPE, Types.SMS_EXPORT_TYPE);
values.put(THREAD_ID, threadId);
values.putNull(BODY);
boolean updated = SQLiteDatabaseExtensionsKt.withinTransaction(getWritableDatabase(), db -> {
if (SignalDatabase.sms().hasSmsExportMessage(threadId)) {
return false;
} else {
db.insert(TABLE_NAME, null, values);
return true;
}
});
if (updated) {
ApplicationDependencies.getDatabaseObserver().notifyConversationListeners(threadId);
}
}
@Override
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
boolean tryToCollapseJoinRequestEvents = false;

Wyświetl plik

@ -236,6 +236,10 @@ public abstract class MessageRecord extends DisplayRecord {
} catch (InvalidProtocolBufferException e) {
throw new AssertionError(e);
}
} else if (isSmsExportType()) {
int messageResource = SignalStore.misc().getSmsExportPhase().isSmsSupported() ? R.string.MessageRecord__you_will_on_longer_be_able_to_send_sms_messages_from_signal_soon
: R.string.MessageRecord__you_can_no_longer_send_sms_messages_in_signal;
return fromRecipient(getIndividualRecipient(), r -> context.getString(messageResource, r.getDisplayName(context)), R.drawable.ic_update_info_16);
}
return null;
@ -542,6 +546,10 @@ public abstract class MessageRecord extends DisplayRecord {
return MmsSmsColumns.Types.isThreadMergeType(type);
}
public boolean isSmsExportType() {
return MmsSmsColumns.Types.isSmsExport(type);
}
public boolean isInvalidVersionKeyExchange() {
return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
}
@ -562,7 +570,7 @@ public abstract class MessageRecord extends DisplayRecord {
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() ||
isProfileChange() || isGroupV1MigrationEvent() || isChatSessionRefresh() || isBadDecryptType() ||
isChangeNumber() || isBoostRequest() || isThreadMergeEventType();
isChangeNumber() || isBoostRequest() || isThreadMergeEventType() || isSmsExportType();
}
public boolean isMediaPending() {

Wyświetl plik

@ -5,6 +5,7 @@ import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
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
@ -12,8 +13,11 @@ import org.thoughtcrime.securesms.attachments.AttachmentId
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.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.notifications.v2.NotificationPendingIntentHelper
import org.thoughtcrime.securesms.util.JsonUtils
import java.io.InputStream
@ -34,16 +38,47 @@ class SignalSmsExportService : SmsExportService() {
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.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.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 getUnexportedMessageCount(): Int {
ensureReader()
return reader!!.getCount()

Wyświetl plik

@ -0,0 +1,25 @@
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.databinding.ExportSmsCompleteFragmentBinding
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
* Shown when export sms completes.
*/
class ExportSmsCompleteFragment : Fragment(R.layout.export_sms_complete_fragment) {
val args: ExportSmsCompleteFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = ExportSmsCompleteFragmentBinding.bind(view)
binding.exportCompleteNext.setOnClickListener { findNavController().safeNavigate(ExportSmsCompleteFragmentDirections.actionExportingSmsMessagesFragmentToChooseANewDefaultSmsAppFragment()) }
binding.exportCompleteStatus.text = getString(R.string.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, args.exportMessageCount)
}
}

Wyświetl plik

@ -4,8 +4,13 @@ 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.SmsExportDirections
import org.thoughtcrime.securesms.databinding.ExportYourSmsMessagesFragmentBinding
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@ -15,6 +20,8 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate
*/
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)
@ -32,4 +39,23 @@ class ExportYourSmsMessagesFragment : Fragment(R.layout.export_your_sms_messages
Material3OnScrollHelper(requireActivity(), binding.toolbar).attach(binding.scrollView)
}
override fun onResume() {
super.onResume()
navigationDisposable = SmsExportService
.progressState
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it is SmsExportProgress.Done) {
findNavController().safeNavigate(SmsExportDirections.actionDirectToExportSmsCompleteFragment(it.progress))
} else if (it is SmsExportProgress.InProgress) {
findNavController().safeNavigate(ExportYourSmsMessagesFragmentDirections.actionExportYourSmsMessagesFragmentToExportingSmsMessagesFragment())
}
}
}
override fun onPause() {
super.onPause()
navigationDisposable.dispose()
}
}

Wyświetl plik

@ -39,7 +39,7 @@ class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fr
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it is SmsExportProgress.Done) {
findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToChooseANewDefaultSmsAppFragment())
findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsCompleteFragment(it.progress))
}
}
}
@ -55,7 +55,7 @@ class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fr
lifecycleDisposable.bindTo(viewLifecycleOwner)
lifecycleDisposable += SmsExportService.progressState.observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) {
SmsExportProgress.Done -> Unit
is SmsExportProgress.Done -> Unit
is SmsExportProgress.InProgress -> {
binding.progress.isIndeterminate = false
binding.progress.max = it.total

Wyświetl plik

@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.exporter.flow
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
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() {
@ -13,6 +15,7 @@ class SmsExportActivity : FragmentWrapperActivity() {
override fun onResume() {
super.onResume()
WindowUtil.setLightStatusBarFromTheme(this)
NotificationManagerCompat.from(this).cancel(NotificationIds.SMS_EXPORT_COMPLETE)
}
override fun getFragment(): Fragment {
@ -20,6 +23,7 @@ class SmsExportActivity : FragmentWrapperActivity() {
}
companion object {
@JvmStatic
fun createIntent(context: Context): Intent = Intent(context, SmsExportActivity::class.java)
}
}

Wyświetl plik

@ -0,0 +1,29 @@
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.sms.deleteExportedMessages()
SignalDatabase.mms.deleteExportedMessages()
}
Snackbar.make(view, R.string.SmsSettingsFragment__removing_sms_messages_from_signal, Snackbar.LENGTH_SHORT).show()
}
.show()
}
}

Wyświetl plik

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
@ -50,8 +51,9 @@ public class CreateGroupActivity extends ContactSelectionActivity {
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.create_group_activity);
int displayMode = Util.isDefaultSmsProvider(context) ? ContactsCursorLoader.DisplayMode.FLAG_SMS | ContactsCursorLoader.DisplayMode.FLAG_PUSH
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
boolean smsEnabled = Util.isDefaultSmsProvider(context) && SignalStore.misc().getSmsExportPhase().isSmsSupported();
int displayMode = smsEnabled ? ContactsCursorLoader.DisplayMode.FLAG_SMS | ContactsCursorLoader.DisplayMode.FLAG_PUSH
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, FeatureFlags.groupLimits().excludingSelf());

Wyświetl plik

@ -5,7 +5,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
public final class MiscellaneousValues extends SignalStoreValues {
@ -26,6 +26,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_EXPORT_TIME = "misc.sms_export_time";
MiscellaneousValues(@NonNull KeyValueStore store) {
super(store);
@ -38,7 +39,9 @@ public final class MiscellaneousValues extends SignalStoreValues {
@Override
@NonNull List<String> getKeysToIncludeInBackup() {
return Collections.emptyList();
return Arrays.asList(
SMS_EXPORT_TIME
);
}
public long getLastPrekeyRefreshTime() {
@ -184,4 +187,15 @@ public final class MiscellaneousValues extends SignalStoreValues {
public void setPniInitializedDevices(boolean value) {
putBoolean(PNI_INITIALIZED_DEVICES, value);
}
public void setHasSeenSmsExportMegaphone() {
if (!getStore().containsKey(SMS_EXPORT_TIME)) {
putLong(SMS_EXPORT_TIME, System.currentTimeMillis());
}
}
public @NonNull SmsExportPhase getSmsExportPhase() {
long now = System.currentTimeMillis();
return SmsExportPhase.getCurrentPhase(now - getLong(SMS_EXPORT_TIME, now));
}
}

Wyświetl plik

@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.keyvalue
import kotlin.time.Duration.Companion.days
enum class SmsExportPhase(val duration: Long) {
PHASE_1(0.days.inWholeMilliseconds),
PHASE_2(45.days.inWholeMilliseconds),
PHASE_3(105.days.inWholeMilliseconds);
fun isSmsSupported(): Boolean {
return this != PHASE_3
}
fun isFullscreen(): Boolean {
return this != PHASE_1
}
fun isBlockingUi(): Boolean {
return this == PHASE_3
}
companion object {
@JvmStatic
fun getCurrentPhase(duration: Long): SmsExportPhase {
return values().findLast { duration >= it.duration }!!
}
}
}

Wyświetl plik

@ -1,5 +1,8 @@
package org.thoughtcrime.securesms.megaphone;
import androidx.annotation.WorkerThread;
public interface MegaphoneSchedule {
@WorkerThread
boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime);
}

Wyświetl plik

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.megaphone;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
@ -22,8 +21,10 @@ import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
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.CreateKbsPinActivity;
@ -39,7 +40,6 @@ import org.thoughtcrime.securesms.util.PlayServicesUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.VersionTracker;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
import java.util.LinkedHashMap;
import java.util.List;
@ -108,6 +108,7 @@ public final class Megaphones {
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : 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);
@ -144,6 +145,8 @@ public final class Megaphones {
return buildRemoteMegaphone(context);
case BACKUP_SCHEDULE_PERMISSION:
return buildBackupPermissionMegaphone(context);
case SMS_EXPORT:
return buildSmsExportMegaphone(context);
default:
throw new IllegalArgumentException("Event not handled!");
}
@ -356,6 +359,35 @@ public final class Megaphones {
.build();
}
private static @NonNull Megaphone buildSmsExportMegaphone(@NonNull Context context) {
SmsExportPhase phase = SignalStore.misc().getSmsExportPhase();
if (phase == SmsExportPhase.PHASE_1) {
return new Megaphone.Builder(Event.SMS_EXPORT, Megaphone.Style.BASIC)
.setTitle(R.string.SmsExportMegaphone__sms_support_going_away)
.setImage(R.drawable.sms_megaphone)
.setBody(R.string.SmsExportMegaphone__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging)
.setActionButton(R.string.SmsExportMegaphone__export_sms, (megaphone, controller) -> controller.onMegaphoneNavigationRequested(SmsExportActivity.createIntent(context), SmsExportMegaphoneActivity.REQUEST_CODE))
.setSecondaryButton(R.string.Megaphones_remind_me_later, (megaphone, controller) -> controller.onMegaphoneSnooze(Event.SMS_EXPORT))
.setOnVisibleListener((megaphone, controller) -> SignalStore.misc().setHasSeenSmsExportMegaphone())
.build();
} else {
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();
}
}
private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Event event, @NonNull Map<Event, MegaphoneRecord> records) {
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(event, records);
@ -452,7 +484,8 @@ public final class Megaphones {
DONATE_Q2_2022("donate_q2_2022"),
TURN_OFF_CENSORSHIP_CIRCUMVENTION("turn_off_censorship_circumvention"),
REMOTE_MEGAPHONE("remote_megaphone"),
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission");
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission"),
SMS_EXPORT("sms_export");
private final String key;

Wyświetl plik

@ -0,0 +1,80 @@
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.SmsExportMegaphoneActivityBinding
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
class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
companion object {
const val REQUEST_CODE: Short = 5343
}
private val theme: DynamicTheme = DynamicNoActionBarTheme()
private lateinit var binding: SmsExportMegaphoneActivityBinding
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
override fun onPreCreate() {
theme.onCreate(this)
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
binding = SmsExportMegaphoneActivityBinding.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() }
if (SignalStore.misc().smsExportPhase.isBlockingUi()) {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_no_longer_supports_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_has_removed_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(false)
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__learn_more)
binding.laterButton.setOnClickListener {
CommunicationActions.openBrowserLink(this, getString(R.string.sms_export_url))
}
} else {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_will_no_longer_support_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_will_soon_remove_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(true)
binding.description.setLink(getString(R.string.sms_export_url))
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__remind_me_later)
binding.laterButton.setOnClickListener {
onBackPressed()
}
}
binding.exportButton.setOnClickListener {
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)
}
}

Wyświetl plik

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.megaphone
import android.content.Context
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.mmsSms
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 {
companion object {
@JvmStatic
var showPhase3Megaphone = true
}
private val basicMegaphoneSchedule = RecurringSchedule(3.days.inWholeMilliseconds)
private val fullScreenSchedule = RecurringSchedule(1.days.inWholeMilliseconds)
@WorkerThread
override fun shouldDisplay(seenCount: Int, lastSeen: Long, firstVisible: Long, currentTime: Long): Boolean {
return if (shouldShowMegaphone()) {
when (SignalStore.misc().smsExportPhase) {
SmsExportPhase.PHASE_1 -> basicMegaphoneSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_2 -> fullScreenSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_3 -> showPhase3Megaphone
}
} else {
false
}
}
@WorkerThread
fun shouldShowMegaphone(): Boolean {
return FeatureFlags.smsExporter() && (Util.isDefaultSmsProvider(context) || mmsSms.unexportedInsecureMessagesCount > 0)
}
}

Wyświetl plik

@ -74,6 +74,7 @@ public class NotificationChannels {
public static final String JOIN_EVENTS = "join_events";
public static final String BACKGROUND = "background_connection";
public static final String CALL_STATUS = "call_status";
public static final String APP_ALERTS = "app_alerts";
/**
* Ensures all of the notification channels are created. No harm in repeat calls. Call is safely
@ -604,6 +605,7 @@ public class NotificationChannels {
NotificationChannel joinEvents = new NotificationChannel(JOIN_EVENTS, context.getString(R.string.NotificationChannel_contact_joined_signal), NotificationManager.IMPORTANCE_DEFAULT);
NotificationChannel background = new NotificationChannel(BACKGROUND, context.getString(R.string.NotificationChannel_background_connection), getDefaultBackgroundChannelImportance(notificationManager));
NotificationChannel callStatus = new NotificationChannel(CALL_STATUS, context.getString(R.string.NotificationChannel_call_status), NotificationManager.IMPORTANCE_LOW);
NotificationChannel appAlerts = new NotificationChannel(APP_ALERTS, context.getString(R.string.NotificationChannel_critical_app_alerts), NotificationManager.IMPORTANCE_HIGH);
messages.setGroup(CATEGORY_MESSAGES);
setVibrationEnabled(messages, SignalStore.settings().isMessageVibrateEnabled());
@ -619,8 +621,9 @@ public class NotificationChannels {
joinEvents.setShowBadge(false);
background.setShowBadge(false);
callStatus.setShowBadge(false);
appAlerts.setShowBadge(false);
notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other, voiceNotes, joinEvents, background, callStatus));
notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other, voiceNotes, joinEvents, background, callStatus, appAlerts));
if (BuildConfig.PLAY_STORE_DISABLED) {
NotificationChannel appUpdates = new NotificationChannel(APP_UPDATES, context.getString(R.string.NotificationChannel_app_updates), NotificationManager.IMPORTANCE_HIGH);

Wyświetl plik

@ -20,6 +20,7 @@ public final class NotificationIds {
public static final int DONOR_BADGE_FAILURE = 630001;
public static final int FCM_FETCH = 630002;
public static final int SMS_EXPORT_SERVICE = 630003;
public static final int SMS_EXPORT_COMPLETE = 630004;
public static final int STORY_THREAD = 700000;
public static final int MESSAGE_DELIVERY_FAILURE = 800000;
public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000;

Wyświetl plik

@ -352,6 +352,11 @@ public class RecipientUtil {
return threadId != null && SignalDatabase.mmsSms().getOutgoingSecureConversationCount(threadId) != 0;
}
public static boolean isSmsOnly(long threadId, @NonNull Recipient threadRecipient) {
return !threadRecipient.isRegistered() ||
noSecureMessagesAndNoCallsInThread(threadId);
}
@WorkerThread
private static boolean noSecureMessagesAndNoCallsInThread(@Nullable Long threadId) {
if (threadId == null) {

Wyświetl plik

@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.B
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
@ -221,10 +222,16 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
unblockButton.setVisibility(View.GONE);
}
boolean isAudioAvailable = (recipient.isRegistered() || (Util.isDefaultSmsProvider(requireContext()) && SignalStore.misc().getSmsExportPhase().isSmsSupported())) &&
!recipient.isGroup() &&
!recipient.isBlocked() &&
!recipient.isSelf() &&
!recipient.isReleaseNotes();
ButtonStripPreference.State buttonStripState = new ButtonStripPreference.State(
/* isMessageAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && !recipient.isReleaseNotes(),
/* isVideoAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && recipient.isRegistered(),
/* isAudioAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && !recipient.isReleaseNotes(),
/* isAudioAvailable = */ isAudioAvailable,
/* isMuteAvailable = */ false,
/* isSearchAvailable = */ false,
/* isAudioSecure = */ recipient.isRegistered(),

Wyświetl plik

@ -1,14 +1,15 @@
package org.thoughtcrime.securesms.util.views;
import android.view.View;
import android.view.ViewStub;
import androidx.annotation.NonNull;
public class Stub<T> {
public class Stub<T extends View> {
private ViewStub viewStub;
private T view;
private T view;
public Stub(@NonNull ViewStub viewStub) {
this.viewStub = viewStub;
@ -16,7 +17,8 @@ public class Stub<T> {
public T get() {
if (view == null) {
view = (T)viewStub.inflate();
//noinspection unchecked
view = (T) viewStub.inflate();
viewStub = null;
}
@ -27,4 +29,10 @@ public class Stub<T> {
return view != null;
}
public void setVisibility(int visibility) {
if (resolved()) {
get().setVisibility(visibility);
}
}
}

Wyświetl plik

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M21.847,5.719C15.943,5.719 11.156,10.505 11.156,16.41V37.792C11.156,43.697 15.943,48.484 21.847,48.484L21.847,56.655C21.847,58.243 23.767,59.037 24.889,57.915L34.321,48.484H43.23C49.134,48.484 53.921,43.697 53.921,37.792V16.41C53.921,10.505 49.134,5.719 43.23,5.719H21.847Z"
android:fillColor="#1B1B1D"
android:fillType="evenOdd"/>
<path
android:pathData="M8.483,16.41C8.483,9.029 14.467,3.046 21.847,3.046H43.23C50.611,3.046 56.594,9.029 56.594,16.41V37.792C56.594,45.173 50.611,51.156 43.23,51.156H35.428L26.779,59.805C23.973,62.611 19.175,60.624 19.175,56.655L19.175,50.889C13.075,49.651 8.483,44.258 8.483,37.792V16.41ZM21.847,48.484C15.943,48.484 11.156,43.697 11.156,37.792V16.41C11.156,10.505 15.943,5.719 21.847,5.719H43.23C49.134,5.719 53.921,10.505 53.921,16.41V37.792C53.921,43.697 49.134,48.484 43.23,48.484H34.321L24.889,57.915C23.767,59.037 21.847,58.243 21.847,56.655L21.847,48.484Z"
android:fillColor="#C1C6DD"
android:fillType="evenOdd"/>
<path
android:pathData="M38.893,15C35.52,15 32.785,17.79 32.785,21.232V25.212C32.478,25.21 32.142,25.21 31.772,25.21H27.822C25.784,25.21 24.765,25.21 23.987,25.615C23.302,25.971 22.745,26.538 22.397,27.237C22,28.031 22,29.071 22,31.15V33.06C22,35.139 22,36.179 22.397,36.973C22.745,37.671 23.302,38.239 23.987,38.595C24.765,39 25.784,39 27.822,39H31.772C33.81,39 34.828,39 35.607,38.595C36.291,38.239 36.848,37.671 37.197,36.973C37.593,36.179 37.593,35.139 37.593,33.06V31.15C37.593,29.071 37.593,28.031 37.197,27.237C36.848,26.538 36.291,25.971 35.607,25.615C35.457,25.537 35.298,25.474 35.124,25.423V21.232C35.124,19.108 36.811,17.387 38.893,17.387C40.974,17.387 42.661,19.108 42.661,21.232V23.088C42.661,23.747 43.185,24.282 43.831,24.282C44.476,24.282 45,23.747 45,23.088V21.232C45,17.79 42.266,15 38.893,15ZM30.706,32.364C31.175,32.058 31.486,31.522 31.486,30.912C31.486,29.96 30.73,29.188 29.797,29.188C28.864,29.188 28.107,29.96 28.107,30.912C28.107,31.522 28.418,32.058 28.887,32.364V34.227C28.887,34.739 29.294,35.155 29.797,35.155C30.299,35.155 30.706,34.739 30.706,34.227V32.364Z"
android:fillColor="#B6C5FA"
android:fillType="evenOdd"/>
</vector>

Wyświetl plik

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M53.965,13.699C55.201,14.749 55.351,16.602 54.301,17.837L26.858,50.135C26.287,50.807 25.443,51.187 24.561,51.169C23.679,51.152 22.852,50.738 22.308,50.043L9.624,33.836C8.625,32.559 8.85,30.713 10.127,29.714C11.404,28.715 13.249,28.94 14.248,30.217L24.714,43.589L49.826,14.035C50.876,12.799 52.729,12.649 53.965,13.699Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

Wyświetl plik

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M21.847,5.719C15.943,5.719 11.156,10.505 11.156,16.41V37.792C11.156,43.697 15.943,48.484 21.847,48.484L21.847,56.655C21.847,58.243 23.767,59.037 24.889,57.915L34.321,48.484H43.23C49.134,48.484 53.921,43.697 53.921,37.792V16.41C53.921,10.505 49.134,5.719 43.23,5.719H21.847Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M8.483,16.41C8.483,9.029 14.467,3.046 21.847,3.046H43.23C50.611,3.046 56.594,9.029 56.594,16.41V37.792C56.594,45.173 50.611,51.156 43.23,51.156H35.428L26.779,59.805C23.973,62.611 19.175,60.624 19.175,56.655L19.175,50.889C13.075,49.651 8.483,44.258 8.483,37.792V16.41ZM21.847,48.484C15.943,48.484 11.156,43.697 11.156,37.792V16.41C11.156,10.505 15.943,5.719 21.847,5.719H43.23C49.134,5.719 53.921,10.505 53.921,16.41V37.792C53.921,43.697 49.134,48.484 43.23,48.484H34.321L24.889,57.915C23.767,59.037 21.847,58.243 21.847,56.655L21.847,48.484Z"
android:fillColor="#B4B9C4"
android:fillType="evenOdd"/>
<path
android:pathData="M38.893,15C35.52,15 32.785,17.79 32.785,21.232V25.212C32.478,25.21 32.142,25.21 31.772,25.21H27.822C25.784,25.21 24.765,25.21 23.987,25.615C23.302,25.971 22.745,26.538 22.397,27.237C22,28.031 22,29.071 22,31.15V33.06C22,35.139 22,36.179 22.397,36.973C22.745,37.671 23.302,38.239 23.987,38.595C24.765,39 25.784,39 27.822,39H31.772C33.81,39 34.828,39 35.607,38.595C36.291,38.239 36.848,37.671 37.197,36.973C37.593,36.179 37.593,35.139 37.593,33.06V31.15C37.593,29.071 37.593,28.031 37.197,27.237C36.848,26.538 36.291,25.971 35.607,25.615C35.457,25.537 35.298,25.474 35.124,25.423V21.232C35.124,19.108 36.811,17.387 38.893,17.387C40.974,17.387 42.661,19.108 42.661,21.232V23.088C42.661,23.747 43.185,24.282 43.831,24.282C44.476,24.282 45,23.747 45,23.088V21.232C45,17.79 42.266,15 38.893,15ZM30.706,32.364C31.175,32.058 31.486,31.522 31.486,30.912C31.486,29.96 30.73,29.188 29.797,29.188C28.864,29.188 28.107,29.96 28.107,30.912C28.107,31.522 28.418,32.058 28.887,32.364V34.227C28.887,34.739 29.294,35.155 29.797,35.155C30.299,35.155 30.706,34.739 30.706,34.227V32.364Z"
android:fillColor="#3A76F0"
android:fillType="evenOdd"/>
</vector>

Wyświetl plik

@ -111,7 +111,7 @@
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:text="@string/AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support"
android:text="@string/AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support_signal_groups_mms_removal"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant" />

Wyświetl plik

@ -134,13 +134,12 @@
android:text="@string/ConversationActivity_unblock"
android:visibility="gone" />
<Button
android:id="@+id/make_default_sms_button"
android:layout_width="fill_parent"
<ViewStub
android:id="@+id/sms_export_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/conversation_activity__enable_signal_for_sms"
android:visibility="gone" />
android:inflatedId="@+id/sms_export_view"
android:layout="@layout/conversation_activity_sms_export_stub" />
<TextView
android:id="@+id/space_left"

Wyświetl plik

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/sms_disabled_learn_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
tools:text="SMS is disabled, export done thanks" />
</LinearLayout>

Wyświetl plik

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/export_sms_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
tools:text="SMS is disabled, export sms now yes" />
<com.google.android.material.button.MaterialButton
android:id="@+id/export_sms_button"
style="@style/Signal.Widget.Button.Large.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
tools:text="Export SMS Messages" />
</LinearLayout>

Wyświetl plik

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.util.views.LearnMoreTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dsl_settings_gutter"
android:layout_marginVertical="16dp"
android:background="@drawable/rounded_outline_12"
android:padding="16dp"
android:textAppearance="@style/Signal.Text.BodyMedium"
tools:text="Mark H is Batman!" />

Wyświetl plik

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/export_complete_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ExportSmsCompleteFragment__export_complete"
android:textAppearance="@style/Signal.Text.TitleLarge"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/export_complete_done_icon"
android:layout_width="112dp"
android:layout_height="112dp"
android:layout_marginBottom="16dp"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/signal_colorSurface2"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@+id/export_complete_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_complete_64"
app:tint="@color/signal_accent_green"
tools:ignore="UnusedAttribute,ImageContrastCheck" />
<TextView
android:id="@+id/export_complete_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/Signal.Text.BodySmall"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/export_complete_title"
tools:text="248 of 248 messages exported" />
<com.google.android.material.button.MaterialButton
android:id="@+id/export_complete_next"
style="@style/Signal.Widget.Button.Large.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
android:layout_marginBottom="24dp"
android:text="@string/ExportSmsCompleteFragment__next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
android:layout_height="match_parent">
<TextView
android:id="@+id/headline"
@ -18,7 +18,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- TODO [alex] - Final text -->
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
@ -26,7 +25,7 @@
android:layout_marginHorizontal="32dp"
android:layout_marginTop="24dp"
android:gravity="center"
tools:text="[WIP]"
android:text="@string/ExportingSmsMessagesFragment__this_may_take_awhile"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
@ -44,15 +43,15 @@
app:layout_constraintWidth_max="260dp" />
<TextView
app:layout_constraintTop_toBottomOf="@id/progress"
android:gravity="center"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="11dp"
tools:text="Exporting 5 of 264..."
android:id="@+id/progress_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="11dp"
android:gravity="center"
android:textAppearance="@style/Signal.Text.BodySmall"
android:textColor="@color/signal_colorOnSurfaceVariant" />
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintTop_toBottomOf="@id/progress"
tools:text="Exporting 5 of 264..." />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="64dp"
android:minHeight="64dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_arrow_left_24" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:importantForAccessibility="no"
android:scaleType="centerInside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/sms_message" />
<TextView
android:id="@+id/headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="40dp"
android:gravity="center"
android:textAppearance="@style/Signal.Text.HeadlineLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image"
tools:text="SMS Export megaphone title" />
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="24dp"
android:gravity="center"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/headline"
tools:text="SMS export megaphone body" />
<com.google.android.material.button.MaterialButton
android:id="@+id/export_button"
style="@style/Signal.Widget.Button.Large.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:minWidth="221dp"
android:text="@string/SmsExportMegaphoneActivity__export_sms"
app:layout_constraintBottom_toTopOf="@+id/later_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/description"
app:layout_constraintVertical_bias="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/later_button"
style="@style/Signal.Widget.Button.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:minWidth="221dp"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1"
tools:text="@string/SmsExportMegaphoneActivity__remind_me_later" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>

Wyświetl plik

@ -34,7 +34,9 @@
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@+id/exportYourSmsMessagesFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
@ -42,6 +44,23 @@
android:name="org.thoughtcrime.securesms.exporter.flow.ExportingSmsMessagesFragment"
android:label="fragment_exporting_sms_messages"
tools:layout="@layout/exporting_sms_messages_fragment">
<action
android:id="@+id/action_exportingSmsMessagesFragment_to_exportSmsCompleteFragment"
app:destination="@id/exportSmsCompleteFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
android:id="@+id/exportSmsCompleteFragment"
android:name="org.thoughtcrime.securesms.exporter.flow.ExportSmsCompleteFragment"
tools:layout="@layout/export_sms_complete_fragment">
<argument android:name="export_message_count"
app:argType="integer" />
<action
android:id="@+id/action_exportingSmsMessagesFragment_to_chooseANewDefaultSmsAppFragment"
app:destination="@id/chooseANewDefaultSmsAppFragment"
@ -49,6 +68,7 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
@ -57,4 +77,14 @@
android:label="fragment_choose_a_new_default_sms_app"
tools:layout="@layout/choose_a_new_default_sms_app_fragment" />
<action
android:id="@+id/action_direct_to_exportSmsCompleteFragment"
app:destination="@id/exportSmsCompleteFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@+id/exportYourSmsMessagesFragment"
app:popUpToInclusive="true"/>
</navigation>

Wyświetl plik

@ -11,6 +11,7 @@
<string name="sustainer_boost_and_badges" translatable="false">https://support.signal.org/hc/articles/4408365318426</string>
<string name="google_pay_url" translatable="false">https://pay.google.com</string>
<string name="donation_decline_code_error_url" translatable="false">https://support.signal.org/hc/articles/4408365318426#errors</string>
<string name="sms_export_url" translatable="false">https://support.signal.org/hc/articles/360007321171</string>
<string name="signal_me_username_url" translatable="false">https://signal.me/#u/%1$s</string>
<string name="signal_me_username_url_no_scheme" translatable="false">signal.me/#u/%1$s</string>
@ -343,6 +344,17 @@
<string name="ConversationActivity__reported_as_spam_and_blocked">Reported as spam and blocked.</string>
<!-- Message shown when opening an SMS conversation with SMS disabled and they have unexported sms messages -->
<string name="ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_you_can_export_your_messages_to_another_app_on_your_phone">SMS messaging is no longer supported in Signal. You can export your messages to another app on your phone.</string>
<!-- Action button shown when in sms conversation, sms is disabled, and unexported sms messages are present -->
<string name="ConversationActivity__export_sms_messages">Export SMS messages</string>
<!-- Message shown when opening an SMS conversation with SMS disabled and there are no exported messages -->
<string name="ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_invite_s_to_to_signal_to_keep_the_conversation_here">SMS messaging is no longer supported in Signal. Invite %1$s to Signal to keep the conversation here.</string>
<!-- Action button shown when opening an SMS conversation with SMS disabled and there are no exported messages -->
<string name="ConversationActivity__invite_to_signal">Invite to Signal</string>
<!-- Snackbar message shown after dismissing the full screen sms export megaphone indicating we'll do it again soon -->
<string name="ConversationActivity__you_will_be_reminded_again_soon">You will be reminded again soon.</string>
<!-- ConversationAdapter -->
<plurals name="ConversationAdapter_n_unread_messages">
<item quantity="one">%d unread message</item>
@ -802,11 +814,11 @@
<string name="AddGroupDetailsFragment__this_field_is_required">This field is required.</string>
<string name="AddGroupDetailsFragment__group_creation_failed">Group creation failed.</string>
<string name="AddGroupDetailsFragment__try_again_later">Try again later.</string>
<!-- Displayed when adding group details to an MMS Group -->
<string name="AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support">You\'ve selected a contact that doesn\'t support Signal groups, so this group will be MMS. Custom MMS group names and photos will only be visible to you.</string>
<string name="AddGroupDetailsFragment__remove">Remove</string>
<string name="AddGroupDetailsFragment__sms_contact">SMS contact</string>
<string name="AddGroupDetailsFragment__remove_s_from_this_group">Remove %1$s from this group?</string>
<!-- Displayed when adding group details to an MMS Group -->
<string name="AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support_signal_groups_mms_removal">You\'ve selected a contact that dosen\'t support Signal groups, this group will be MMS. Custom MMS group names and photos will only be visible to you. Support for MMS groups will be removed soon to focus on encrypted messaging.</string>
<!-- ManageGroupActivity -->
<string name="ManageGroupActivity_member_requests_and_invites">Member requests &amp; invites</string>
@ -990,6 +1002,8 @@
<!-- InputPanel -->
<string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Tap and hold to record a voice message, release to send</string>
<!-- Message shown if the user tries to switch a conversation from Signal to SMS -->
<string name="InputPanel__sms_messaging_is_no_longer_supported_in_signal">SMS messaging is no longer supported in Signal.</string>
<!-- InviteActivity -->
<string name="InviteActivity_share">Share</string>
@ -1375,6 +1389,11 @@
<item quantity="other">%1$s, %2$s, and %3$d others are in the group call</item>
</plurals>
<!-- In-conversation update message to indicate that the current contact is sms only and will need to migrate to signal to continue the conversation in signal. -->
<string name="MessageRecord__you_will_on_longer_be_able_to_send_sms_messages_from_signal_soon">You will on longer be able to send SMS messages from Signal soon. Invite %1$s to Signal to keep the conversation here.</string>
<!-- In-conversation update message to indicate that the current contact is sms only and will need to migrate to signal to continue the conversation in signal. -->
<string name="MessageRecord__you_can_no_longer_send_sms_messages_in_signal">You can no longer send SMS messages in Signal. Invite %1$s to Signal to keep the conversation here.</string>
<!-- MessageRequestBottomView -->
<string name="MessageRequestBottomView_accept">Accept</string>
<string name="MessageRequestBottomView_continue">Continue</string>
@ -2017,6 +2036,9 @@
<string name="NotificationChannel_background_connection">Background connection</string>
<!-- Notification channel name for showing call status information (like connection, ongoing, etc.) Not ringing. -->
<string name="NotificationChannel_call_status">Call status</string>
<!-- Notification channel name for occasional alerts to the user -->
<string name="NotificationChannel_critical_app_alerts">Critical app alerts</string>
<!-- ProfileEditNameFragment -->
<!-- QuickResponseService -->
@ -3976,6 +3998,12 @@
<string name="SmsSettingsFragment__removing_sms_messages_from_signal">Removing SMS messages from Signal…</string>
<!-- Snackbar text to indicate can delete later -->
<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 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. -->
<string name="SmsSettingsFragment__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging">SMS support will be removed soon to focus on encrypted messaging.</string>
<!-- NotificationsSettingsFragment -->
<string name="NotificationsSettingsFragment__messages">Messages</string>
@ -4236,6 +4264,8 @@
<string name="NewConversationActivity__this_person_is_saved_to_your">This person is saved to your device\'s Contacts. Delete them from your Contacts and try again.</string>
<!-- Dialog action to view contact when they can't be removed otherwise -->
<string name="NewConversationActivity__view_contact">View contact</string>
<!-- Error message shown when looking up a person by phone number and that phone number is not associated with a signal account -->
<string name="NewConversationActivity__s_is_not_a_signal_user">%1$s is not a Signal user</string>
<!-- ContactFilterView -->
<string name="ContactFilterView__search_name_or_number">Search name or number</string>
@ -5289,6 +5319,10 @@
<!-- SMS Export Service -->
<!-- Displayed in the notification while export is running -->
<string name="SignalSmsExportService__exporting_messages">Exporting messages…</string>
<!-- Displayed in the notification title when export completes -->
<string name="SignalSmsExportService__signal_sms_export_complete">Signal SMS Export Complete</string>
<!-- Displayed in the notification message when export completes -->
<string name="SignalSmsExportService__tap_to_return_to_signal">Tap to return to Signal</string>
<!-- ExportYourSmsMessagesFragment -->
<!-- Title of the screen -->
@ -5301,6 +5335,8 @@
<!-- ExportingSmsMessagesFragment -->
<!-- Title of the screen -->
<string name="ExportingSmsMessagesFragment__exporting_sms_messages">Exporting SMS messages</string>
<!-- Message of the screen when exporting sms messages -->
<string name="ExportingSmsMessagesFragment__this_may_take_awhile">This may take awhile</string>
<!-- Progress indicator for export -->
<string name="ExportingSmsMessagesFragment__exporting_d_of_d">Exporting %1$d of %2$d…</string>
@ -5368,6 +5404,35 @@
<!-- Re-enable backups permission bottom sheet call to action button to open settings -->
<string name="BackupSchedulePermissionMegaphone__go_to_settings">Go to settings</string>
<!-- SmsExportMegaphoneActivity -->
<!-- Phase 2 title of full screen megaphone indicating sms will no longer be supported in the near future -->
<string name="SmsExportMegaphoneActivity__signal_will_no_longer_support_sms">Signal will no longer support SMS</string>
<!-- Phase 3 title of full screen megaphone indicating sms is longer supported -->
<string name="SmsExportMegaphoneActivity__signal_no_longer_supports_sms">Signal no longer supports SMS</string>
<!-- Phase 2 message describing that sms is going away soon -->
<string name="SmsExportMegaphoneActivity__signal_will_soon_remove_support_for_sending_sms_messages">Signal will soon remove support for sending SMS messages because Signal messages provide end-to-end encryption and strong privacy that SMS messages don\'t. This will also allow us to improve the Signal messaging experience.</string>
<!-- Phase 3 message describing that sms has gone away -->
<string name="SmsExportMegaphoneActivity__signal_has_removed_support_for_sending_sms_messages">Signal has removed support for sending SMS messages because Signal messages provide end-to-end encryption and strong privacy that SMS messages don\'t. This will also allow us to improve the Signal messaging experience.</string>
<!-- Button on full screen megaphone that takes the user to the export messages flow -->
<string name="SmsExportMegaphoneActivity__export_sms">Export SMS</string>
<!-- Button on full screen megaphone that dismiss the megaphone and it'll be shown again at a later time -->
<string name="SmsExportMegaphoneActivity__remind_me_later">Remind me later</string>
<!-- Button on full screen megaphone that navigates to a web article on sms removal -->
<string name="SmsExportMegaphoneActivity__learn_more">Learn more</string>
<!-- Phase 1 Small megaphone title indicating sms is going away -->
<string name="SmsExportMegaphone__sms_support_going_away">SMS support going away</string>
<!-- Phase 1 small megaphone description indicating sms is going away -->
<string name="SmsExportMegaphone__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging">SMS support will be removed soon to focus on encrypted messaging.</string>
<!-- Phase 1 small megaphone button that takes the user to the sms export flow -->
<string name="SmsExportMegaphone__export_sms">Export SMS</string>
<!-- Title for screen shown after sms export has completed -->
<string name="ExportSmsCompleteFragment__export_complete">Export Complete</string>
<!-- Button to continue to next screen -->
<string name="ExportSmsCompleteFragment__next">Next</string>
<!-- Message showing summary of sms export counts -->
<string name="ExportSmsCompleteFragment__d_of_d_messages_exported">%1$d of %2$d messages exported</string>
<!-- EOF -->
</resources>

Wyświetl plik

@ -50,9 +50,11 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.OUTGOING_VIDEO_CA
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.PROFILE_CHANGE_TYPE
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.PUSH_MESSAGE_BIT
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.SECURE_MESSAGE_BIT
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.SMS_EXPORT_TYPE
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.SPECIAL_TYPES_MASK
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.SPECIAL_TYPE_GIFT_BADGE
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.SPECIAL_TYPE_STORY_REACTION
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.THREAD_MERGE_TYPE
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types.UNSUPPORTED_MESSAGE_TYPE
object MessageBitmaskColumnTransformer : ColumnTransformer {
@ -111,6 +113,8 @@ object MessageBitmaskColumnTransformer : ColumnTransformer {
isGroupV1MigrationEvent:${type == GV1_MIGRATION_TYPE}
isChangeNumber:${type == CHANGE_NUMBER_TYPE}
isBoostRequest:${type == BOOST_REQUEST_TYPE}
isThreadMerge:${type == THREAD_MERGE_TYPE}
isSmsExport:${type == SMS_EXPORT_TYPE}
isGroupV2LeaveOnly:${type and GROUP_V2_LEAVE_BITS == GROUP_V2_LEAVE_BITS}
isSpecialType:${type and SPECIAL_TYPES_MASK != 0L}
isStoryReaction:${type and SPECIAL_TYPES_MASK == SPECIAL_TYPE_STORY_REACTION}

Wyświetl plik

@ -81,6 +81,9 @@ class SmsDatabaseTest {
TestSms.insert(db, type = MmsSmsColumns.Types.BOOST_REQUEST_TYPE)
assertFalse(smsDatabase.hasMeaningfulMessage(1))
TestSms.insert(db, type = MmsSmsColumns.Types.SMS_EXPORT_TYPE)
assertFalse(smsDatabase.hasMeaningfulMessage(1))
TestSms.insert(db, type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.GROUP_V2_LEAVE_BITS)
assertFalse(smsDatabase.hasMeaningfulMessage(1))
}

Wyświetl plik

@ -50,6 +50,13 @@ fun SupportSQLiteDatabase.select(vararg columns: String): SelectBuilderPart1 {
return SelectBuilderPart1(this, arrayOf(*columns))
}
/**
* Begins a COUNT statement with a helpful builder pattern.
*/
fun SupportSQLiteDatabase.count(): SelectBuilderPart1 {
return SelectBuilderPart1(this, SqlUtil.COUNT)
}
/**
* Begins an UPDATE statement with a helpful builder pattern.
*/

Wyświetl plik

@ -38,7 +38,7 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
disposables += SmsExportService.progressState.onBackpressureLatest().subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) {
SmsExportProgress.Done -> {
is SmsExportProgress.Done -> {
exportStatus.text = "Done"
exportProgress.isVisible = true
}

Wyświetl plik

@ -33,6 +33,10 @@ class TestSmsExportService : SmsExportService() {
)
}
override fun getExportCompleteNotification(): ExportNotification? {
return null
}
override fun getUnexportedMessageCount(): Int {
return 50
}

Wyświetl plik

@ -25,5 +25,5 @@ sealed class SmsExportProgress {
/**
* All done.
*/
object Done : SmsExportProgress()
data class Done(val progress: Int) : SmsExportProgress()
}

Wyświetl plik

@ -4,6 +4,7 @@ import android.app.Notification
import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationManagerCompat
import io.reactivex.rxjava3.processors.BehaviorProcessor
import org.signal.core.util.Result
import org.signal.core.util.Try
@ -78,7 +79,12 @@ abstract class SmsExportService : Service() {
}
onExportPassCompleted()
progressState.onNext(SmsExportProgress.Done)
progressState.onNext(SmsExportProgress.Done(progress))
getExportCompleteNotification()?.let { notification ->
NotificationManagerCompat.from(this).notify(notification.id, notification.notification)
}
stopForeground(true)
isStarted = false
}
@ -97,6 +103,13 @@ abstract class SmsExportService : Service() {
*/
protected abstract fun getNotification(progress: Int, total: Int): ExportNotification
/**
* Produces the notification and notification id to display when the export is complete.
*
* Can be null if no notification is needed (e.g., the user is still in the app)
*/
protected abstract fun getExportCompleteNotification(): ExportNotification?
/**
* Gets the total number of messages to process. This is only used for the notification and
* progress events.