mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-29 13:16:01 +01:00
Add phased SMS removal UX.
This commit is contained in:
committed by
Greyson Parrelli
parent
8a238a66e7
commit
b6db7e7af6
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user