diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 7a24c364a4..fd3fa9abbe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -159,6 +159,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr .addNonBlocking(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this)) .addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this)) .addPostRender(this::initializeExpiringMessageManager) + .addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this))) .execute(); Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt index a54df47451..455ac070f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt @@ -12,7 +12,9 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter 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.keyvalue.SignalStore import org.thoughtcrime.securesms.util.SmsUtil +import org.thoughtcrime.securesms.util.Util private const val SMS_REQUEST_CODE: Short = 1234 @@ -33,6 +35,10 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) { } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(requireContext())) + } + private fun getConfiguration(state: SmsSettingsState): DSLConfiguration { return configure { clickPref( @@ -79,12 +85,12 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) { // Linter isn't smart enough to figure out the else only happens if API >= 24 @SuppressLint("InlinedApi") private fun startDefaultAppSelectionIntent() { - startActivity( - when { - Build.VERSION.SDK_INT < 23 -> Intent(Settings.ACTION_WIRELESS_SETTINGS) - Build.VERSION.SDK_INT < 24 -> Intent(Settings.ACTION_SETTINGS) - else -> Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) - } - ) + val intent: Intent = when { + Build.VERSION.SDK_INT < 23 -> Intent(Settings.ACTION_WIRELESS_SETTINGS) + Build.VERSION.SDK_INT < 24 -> Intent(Settings.ACTION_SETTINGS) + else -> Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + } + + startActivityForResult(intent, SMS_REQUEST_CODE.toInt()) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 6dc1fa2b4e..c89d2b76da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -249,6 +249,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerManagementActivity; import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent; import org.thoughtcrime.securesms.stickers.StickerSearchRepository; +import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.util.AsynchronousCallback; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.BitmapUtil; @@ -643,6 +644,7 @@ public class ConversationActivity extends PassphraseRequiredActivity (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) { updateLinkPreviewState(); + SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index a02034cbe8..52a1d0ce39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMi import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RetrieveProfileJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigration; +import org.thoughtcrime.securesms.migrations.AccountRecordMigrationJob; import org.thoughtcrime.securesms.migrations.AttributesMigrationJob; import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob; import org.thoughtcrime.securesms.migrations.AvatarMigrationJob; @@ -157,6 +158,7 @@ public final class JobManagerFactories { put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory()); // Migrations + put(AccountRecordMigrationJob.KEY, new AccountRecordMigrationJob.Factory()); put(AttributesMigrationJob.KEY, new AttributesMigrationJob.Factory()); put(AvatarIdRemovalMigrationJob.KEY, new AvatarIdRemovalMigrationJob.Factory()); put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index e0cb9a95c8..6bb27551c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.keyvalue; +import android.content.Context; import android.net.Uri; import android.provider.Settings; import android.text.TextUtils; @@ -9,10 +10,14 @@ import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.signal.core.util.concurrent.SignalExecutors; +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.webrtc.CallBandwidthMode; import java.util.Arrays; @@ -21,6 +26,8 @@ import java.util.List; @SuppressWarnings("deprecation") public final class SettingsValues extends SignalStoreValues { + private static final String TAG = Log.tag(SettingsValues.class); + public static final String LINK_PREVIEWS = "settings.link_previews"; public static final String KEEP_MESSAGES_DURATION = "settings.keep_messages_duration"; @@ -57,6 +64,8 @@ public final class SettingsValues extends SignalStoreValues { private final SingleLiveEvent onConfigurationSettingChanged = new SingleLiveEvent<>(); + private static final String DEFAULT_SMS = "settings.default_sms"; + SettingsValues(@NonNull KeyValueStore store) { super(store); } @@ -341,6 +350,25 @@ public final class SettingsValues extends SignalStoreValues { putBoolean(NOTIFY_WHEN_CONTACT_JOINS_SIGNAL, notifyWhenContactJoinsSignal); } + /** + * We need to keep track of when the default status changes so we can sync to storage service. + * So call this when you think it might have changed, but *don't* rely on it for knowing if we + * *are* the default SMS. For that, continue to use + * {@link org.thoughtcrime.securesms.util.Util#isDefaultSmsProvider(Context)}. + */ + public void setDefaultSms(boolean value) { + boolean lastKnown = getBoolean(DEFAULT_SMS, false); + + if (value != lastKnown) { + Log.i(TAG, "Default SMS state changed! Scheduling a storage sync."); + putBoolean(DEFAULT_SMS, value); + + SignalExecutors.BOUNDED.execute(() -> { + DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).markNeedsSync(Recipient.self().getId()); + StorageSyncHelper.scheduleSyncForDataChange(); + }); + } + } private @Nullable Uri getUri(@NonNull String key) { String uri = getString(key, ""); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/AccountRecordMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/AccountRecordMigrationJob.java new file mode 100644 index 0000000000..700bc3fcd6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/AccountRecordMigrationJob.java @@ -0,0 +1,64 @@ +package org.thoughtcrime.securesms.migrations; + +import androidx.annotation.NonNull; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobs.StorageSyncJob; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +/** + * Marks the AccountRecord as dirty and runs a storage sync. Can be enqueued when we've added a new + * attribute to the AccountRecord. + */ +public class AccountRecordMigrationJob extends MigrationJob { + + private static final String TAG = Log.tag(AccountRecordMigrationJob.class); + + public static final String KEY = "AccountRecordMigrationJob"; + + AccountRecordMigrationJob() { + this(new Parameters.Builder().build()); + } + + private AccountRecordMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public boolean isUiBlocking() { + return false; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void performMigration() { + if (!TextSecurePreferences.isPushRegistered(context) || TextSecurePreferences.getLocalUuid(context) == null) { + Log.w(TAG, "Not registered!"); + return; + } + + DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().getId()); + ApplicationDependencies.getJobManager().add(new StorageSyncJob()); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull AccountRecordMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new AccountRecordMigrationJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index aead2e9aa9..81458f45ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -40,7 +40,7 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 32; + public static final int CURRENT_VERSION = 33; private static final class Version { static final int LEGACY = 1; @@ -74,6 +74,7 @@ public class ApplicationMigrations { // Versions 29, 30 accidentally skipped static final int MUTE_SYNC = 31; static final int PROFILE_SHARING_UPDATE = 32; + static final int SMS_STORAGE_SYNC = 33; } /** @@ -312,6 +313,10 @@ public class ApplicationMigrations { jobs.put(Version.PROFILE_SHARING_UPDATE, new ProfileSharingUpdateMigrationJob()); } + if (lastSeenVersion < Version.SMS_STORAGE_SYNC) { + jobs.put(Version.SMS_STORAGE_SYNC, new AccountRecordMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java index a894551b12..9c3f02a476 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java @@ -28,20 +28,18 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor pinnedConversations = remote.getPinnedConversations(); AccountRecord.PhoneNumberSharingMode phoneNumberSharingMode = remote.getPhoneNumberSharingMode(); boolean preferContactAvatars = remote.isPreferContactAvatars(); - boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments); - boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments); + boolean primarySendsSms = local.isPrimarySendsSms(); + boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, primarySendsSms); + boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, primarySendsSms); if (matchesRemote) { return remote; @@ -125,6 +124,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor pinnedConversations, boolean preferContactAvatars, - SignalAccountRecord.Payments payments) + SignalAccountRecord.Payments payments, + boolean primarySendsSms) { return Arrays.equals(contact.serializeUnknownFields(), unknownFields) && Objects.equals(contact.getGivenName().or(""), givenName) && @@ -177,6 +178,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor avatarUrlPath; private final Optional profileKey; private final List pinnedConversations; - private final boolean preferContactAvatars; private final Payments payments; public SignalAccountRecord(StorageId id, AccountRecord proto) { @@ -41,7 +40,6 @@ public final class SignalAccountRecord implements SignalRecord { this.profileKey = OptionalUtil.absentIfEmpty(proto.getProfileKey()); this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.getAvatarUrlPath()); this.pinnedConversations = new ArrayList<>(proto.getPinnedConversationsCount()); - this.preferContactAvatars = proto.getPreferContactAvatars(); this.payments = new Payments(proto.getPayments().getEnabled(), OptionalUtil.absentIfEmpty(proto.getPayments().getEntropy())); for (AccountRecord.PinnedConversation conversation : proto.getPinnedConversationsList()) { @@ -121,7 +119,7 @@ public final class SignalAccountRecord implements SignalRecord { diff.add("PinnedConversations"); } - if (!Objects.equals(this.preferContactAvatars, that.preferContactAvatars)) { + if (!Objects.equals(this.isPreferContactAvatars(), that.isPreferContactAvatars())) { diff.add("PreferContactAvatars"); } @@ -129,6 +127,10 @@ public final class SignalAccountRecord implements SignalRecord { diff.add("Payments"); } + if (!Objects.equals(this.isPrimarySendsSms(), that.isPrimarySendsSms())) { + diff.add("PrimarySendsSms"); + } + if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) { diff.add("UnknownFields"); } @@ -200,13 +202,17 @@ public final class SignalAccountRecord implements SignalRecord { } public boolean isPreferContactAvatars() { - return preferContactAvatars; + return proto.getPreferContactAvatars(); } public Payments getPayments() { return payments; } + public boolean isPrimarySendsSms() { + return proto.getPrimarySendsSms(); + } + AccountRecord toProto() { return proto; } @@ -442,7 +448,6 @@ public final class SignalAccountRecord implements SignalRecord { public Builder setPreferContactAvatars(boolean preferContactAvatars) { builder.setPreferContactAvatars(preferContactAvatars); - return this; } @@ -462,6 +467,11 @@ public final class SignalAccountRecord implements SignalRecord { return this; } + public Builder setPrimarySendsSms(boolean primarySendsSms) { + builder.setPrimarySendsSms(primarySendsSms); + return this; + } + public SignalAccountRecord build() { AccountRecord proto = builder.build(); diff --git a/libsignal/service/src/main/proto/SignalStorage.proto b/libsignal/service/src/main/proto/SignalStorage.proto index 118bee0ae6..b9af99e22f 100644 --- a/libsignal/service/src/main/proto/SignalStorage.proto +++ b/libsignal/service/src/main/proto/SignalStorage.proto @@ -144,4 +144,5 @@ message AccountRecord { repeated PinnedConversation pinnedConversations = 14; bool preferContactAvatars = 15; Payments payments = 16; + bool primarySendsSms = 18; }