mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-20 17:57:29 +00:00
Remove GV1 migration support.
This commit is contained in:
committed by
Greyson Parrelli
parent
213d996168
commit
34a228f85e
@@ -32,8 +32,4 @@ sealed class ConversationSettingsEvent {
|
||||
class ShowMembersAdded(
|
||||
val membersAddedCount: Int
|
||||
) : ConversationSettingsEvent()
|
||||
|
||||
class InitiateGroupMigration(
|
||||
val recipientId: RecipientId
|
||||
) : ConversationSettingsEvent()
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndR
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentDialog
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupsLearnMoreBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
|
||||
@@ -279,7 +278,6 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
is ConversationSettingsEvent.ShowAddMembersToGroupError -> showAddMembersToGroupError(event)
|
||||
is ConversationSettingsEvent.ShowGroupInvitesSentDialog -> showGroupInvitesSentDialog(event)
|
||||
is ConversationSettingsEvent.ShowMembersAdded -> showMembersAdded(event)
|
||||
is ConversationSettingsEvent.InitiateGroupMigration -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(parentFragmentManager, event.recipientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +361,6 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
LegacyGroupPreference.Model(
|
||||
state = groupState.legacyGroupState,
|
||||
onLearnMoreClick = { GroupsLearnMoreBottomSheetDialogFragment.show(parentFragmentManager) },
|
||||
onUpgradeClick = { viewModel.initiateGroupUpgrade() },
|
||||
onMmsWarningClick = { startActivity(Intent(requireContext(), InviteActivity::class.java)) }
|
||||
)
|
||||
)
|
||||
|
||||
@@ -30,7 +30,6 @@ 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.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
@@ -119,8 +118,6 @@ sealed class ConversationSettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
open fun initiateGroupUpgrade(): Unit = error("This ViewModel does not support this interaction")
|
||||
|
||||
private class RecipientSettingsViewModel(
|
||||
private val recipientId: RecipientId,
|
||||
private val callMessageIds: LongArray,
|
||||
@@ -281,7 +278,7 @@ sealed class ConversationSettingsViewModel(
|
||||
),
|
||||
canModifyBlockedState = RecipientUtil.isBlockable(recipient),
|
||||
specificSettingsState = state.requireGroupSettingsState().copy(
|
||||
legacyGroupState = getLegacyGroupState(recipient)
|
||||
legacyGroupState = getLegacyGroupState()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -390,14 +387,8 @@ sealed class ConversationSettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLegacyGroupState(recipient: Recipient): LegacyGroupPreference.State {
|
||||
val showLegacyInfo = recipient.requireGroupId().isV1
|
||||
|
||||
return if (showLegacyInfo && recipient.participantIds.size > FeatureFlags.groupLimits().hardLimit) {
|
||||
LegacyGroupPreference.State.TOO_LARGE
|
||||
} else if (showLegacyInfo) {
|
||||
LegacyGroupPreference.State.UPGRADE
|
||||
} else if (groupId.isMms) {
|
||||
private fun getLegacyGroupState(): LegacyGroupPreference.State {
|
||||
return if (groupId.isMms) {
|
||||
LegacyGroupPreference.State.MMS_WARNING
|
||||
} else {
|
||||
LegacyGroupPreference.State.NONE
|
||||
@@ -468,12 +459,6 @@ sealed class ConversationSettingsViewModel(
|
||||
override fun unblock() {
|
||||
repository.unblock(groupId)
|
||||
}
|
||||
|
||||
override fun initiateGroupUpgrade() {
|
||||
repository.getExternalPossiblyMigratedGroupRecipientId(groupId) {
|
||||
internalEvents.onNext(ConversationSettingsEvent.InitiateGroupMigration(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
@@ -19,7 +18,6 @@ object LegacyGroupPreference {
|
||||
class Model(
|
||||
val state: State,
|
||||
val onLearnMoreClick: () -> Unit,
|
||||
val onUpgradeClick: () -> Unit,
|
||||
val onMmsWarningClick: () -> Unit
|
||||
) : PreferenceModel<Model>() {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
@@ -42,15 +40,6 @@ object LegacyGroupPreference {
|
||||
groupInfoText.setOnLinkClickListener { model.onLearnMoreClick() }
|
||||
groupInfoText.setLearnMoreVisible(true)
|
||||
}
|
||||
State.UPGRADE -> {
|
||||
groupInfoText.setText(R.string.ManageGroupActivity_legacy_group_upgrade)
|
||||
groupInfoText.setOnLinkClickListener { model.onUpgradeClick() }
|
||||
groupInfoText.setLearnMoreVisible(true, R.string.ManageGroupActivity_upgrade_this_group)
|
||||
}
|
||||
State.TOO_LARGE -> {
|
||||
groupInfoText.text = context.getString(R.string.ManageGroupActivity_legacy_group_too_large, FeatureFlags.groupLimits().hardLimit - 1)
|
||||
groupInfoText.setLearnMoreVisible(false)
|
||||
}
|
||||
State.MMS_WARNING -> {
|
||||
groupInfoText.setText(R.string.ManageGroupActivity_this_is_an_insecure_mms_group)
|
||||
groupInfoText.setOnLinkClickListener { model.onMmsWarningClick() }
|
||||
@@ -63,8 +52,6 @@ object LegacyGroupPreference {
|
||||
|
||||
enum class State {
|
||||
LEARN_MORE,
|
||||
UPGRADE,
|
||||
TOO_LARGE,
|
||||
MMS_WARNING,
|
||||
NONE
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
@@ -119,14 +118,6 @@ internal object ConversationOptionsMenu {
|
||||
}
|
||||
}
|
||||
menuInflater.inflate(R.menu.conversation_group_options, menu)
|
||||
if (!recipient.isPushGroup) {
|
||||
menuInflater.inflate(R.menu.conversation_mms_group_options, menu)
|
||||
if (distributionType == ThreadTable.DistributionTypes.BROADCAST) {
|
||||
menu.findItem(R.id.menu_distribution_broadcast).isChecked = true
|
||||
} else {
|
||||
menu.findItem(R.id.menu_distribution_conversation).isChecked = true
|
||||
}
|
||||
}
|
||||
menuInflater.inflate(R.menu.conversation_active_group_options, menu)
|
||||
}
|
||||
|
||||
@@ -216,8 +207,6 @@ internal object ConversationOptionsMenu {
|
||||
R.id.menu_search -> callback.handleSearch()
|
||||
R.id.menu_add_to_contacts -> callback.handleAddToContacts()
|
||||
R.id.menu_group_recipients -> callback.handleDisplayGroupRecipients()
|
||||
R.id.menu_distribution_broadcast -> callback.handleDistributionBroadcastEnabled(menuItem)
|
||||
R.id.menu_distribution_conversation -> callback.handleDistributionConversationEnabled(menuItem)
|
||||
R.id.menu_group_settings -> callback.handleManageGroup()
|
||||
R.id.menu_leave -> callback.handleLeavePushGroup()
|
||||
R.id.menu_invite -> callback.handleInviteLink()
|
||||
@@ -283,8 +272,6 @@ internal object ConversationOptionsMenu {
|
||||
fun handleSearch()
|
||||
fun handleAddToContacts()
|
||||
fun handleDisplayGroupRecipients()
|
||||
fun handleDistributionBroadcastEnabled(menuItem: MenuItem)
|
||||
fun handleDistributionConversationEnabled(menuItem: MenuItem)
|
||||
fun handleManageGroup()
|
||||
fun handleLeavePushGroup()
|
||||
fun handleInviteLink()
|
||||
|
||||
@@ -219,7 +219,6 @@ import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndR
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationSuggestionsDialog
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult
|
||||
import org.thoughtcrime.securesms.invites.InviteActions
|
||||
@@ -3144,10 +3143,6 @@ class ConversationFragment :
|
||||
GroupMembersDialog(requireActivity(), recipientSnapshot).display()
|
||||
}
|
||||
|
||||
override fun handleDistributionBroadcastEnabled(menuItem: MenuItem) = error("This fragment does not support this action.")
|
||||
|
||||
override fun handleDistributionConversationEnabled(menuItem: MenuItem) = error("This fragment does not support this action.")
|
||||
|
||||
override fun handleManageGroup() {
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
val intent = ConversationSettingsActivity.forGroup(requireContext(), recipient.requireGroupId())
|
||||
@@ -3612,16 +3607,6 @@ class ConversationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGroupV1MigrationClicked() {
|
||||
val recipient = viewModel.recipientSnapshot
|
||||
if (recipient == null) {
|
||||
Log.w(TAG, "[onGroupV1MigrationClicked] No recipient!")
|
||||
return
|
||||
}
|
||||
|
||||
GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(childFragmentManager, recipient.id)
|
||||
}
|
||||
|
||||
override fun onInviteToSignal(recipient: Recipient) {
|
||||
InviteActions.inviteUserToSignal(
|
||||
context = requireContext(),
|
||||
|
||||
@@ -87,7 +87,6 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
setDeleteOnClickListener { listener?.onDeleteGroupClicked() }
|
||||
setBlockOnClickListener { listener?.onBlockClicked() }
|
||||
setUnblockOnClickListener { listener?.onUnblockClicked() }
|
||||
setGroupV1MigrationContinueListener { listener?.onGroupV1MigrationClicked() }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -230,7 +229,6 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
fun onDeleteGroupClicked()
|
||||
fun onBlockClicked()
|
||||
fun onUnblockClicked()
|
||||
fun onGroupV1MigrationClicked()
|
||||
fun onInviteToSignal(recipient: Recipient)
|
||||
fun onUnmuteReleaseNotesChannel()
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupId.Push
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -658,15 +657,9 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
|
||||
@JvmOverloads
|
||||
@CheckReturnValue
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, force: Boolean = false): GroupId.V2? {
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup): GroupId.V2? {
|
||||
val groupId = GroupId.v2(groupMasterKey)
|
||||
|
||||
if (!force && GroupsV1MigratedCache.hasV1Group(groupId)) {
|
||||
throw MissedGroupMigrationInsertException(groupId)
|
||||
} else if (force) {
|
||||
Log.w(TAG, "Forcing the creation of a group even though we already have a V1 ID!")
|
||||
}
|
||||
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState)) {
|
||||
groupId
|
||||
} else {
|
||||
@@ -680,9 +673,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
*/
|
||||
fun fixMissingMasterKey(groupMasterKey: GroupMasterKey) {
|
||||
val groupId = GroupId.v2(groupMasterKey)
|
||||
if (GroupsV1MigratedCache.hasV1Group(groupId)) {
|
||||
Log.w(TAG, "There already exists a V1 group that should be migrated into this group. But if the recipient already exists, there's not much we can do here.")
|
||||
}
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val updated = db
|
||||
@@ -697,8 +687,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
groupMasterKey,
|
||||
DecryptedGroup.Builder()
|
||||
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build(),
|
||||
true
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "Had a group entry, but it was missing a master key. Updated.")
|
||||
@@ -1526,5 +1515,4 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
}
|
||||
|
||||
class LegacyGroupInsertException(id: GroupId?) : IllegalStateException("Tried to create a new GV1 entry when we already had a migrated GV2! $id")
|
||||
class MissedGroupMigrationInsertException(id: GroupId?) : IllegalStateException("Tried to create a new GV2 entry when we already had a V1 group that mapped to the new ID! $id")
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColors.Id.Companion.fo
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper.getChatColors
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.GroupTable.LegacyGroupInsertException
|
||||
import org.thoughtcrime.securesms.database.GroupTable.MissedGroupMigrationInsertException
|
||||
import org.thoughtcrime.securesms.database.GroupTable.ShowAsStoryState
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
|
||||
@@ -80,7 +79,6 @@ import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupId.V1
|
||||
import org.thoughtcrime.securesms.groups.GroupId.V2
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache
|
||||
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
|
||||
@@ -574,8 +572,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
return existing.get()
|
||||
} else if (groupId.isV1 && groups.groupExists(groupId.requireV1().deriveV2MigrationGroupId())) {
|
||||
throw LegacyGroupInsertException(groupId)
|
||||
} else if (groupId.isV2 && GroupsV1MigratedCache.hasV1Group(groupId.requireV2())) {
|
||||
throw MissedGroupMigrationInsertException(groupId)
|
||||
} else {
|
||||
val values = ContentValues().apply {
|
||||
put(GROUP_ID, groupId.toString())
|
||||
@@ -589,8 +585,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
return existing.get()
|
||||
} else if (groupId.isV1 && groups.groupExists(groupId.requireV1().deriveV2MigrationGroupId())) {
|
||||
throw LegacyGroupInsertException(groupId)
|
||||
} else if (groupId.isV2 && groups.getGroupV1ByExpectedV2(groupId.requireV2()).isPresent) {
|
||||
throw MissedGroupMigrationInsertException(groupId)
|
||||
} else {
|
||||
throw AssertionError("Failed to insert recipient!")
|
||||
}
|
||||
@@ -644,14 +638,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
if (groupId.isV2) {
|
||||
val v1 = GroupsV1MigratedCache.getV1GroupByV2Id(groupId.requireV2())
|
||||
if (v1 != null) {
|
||||
db.setTransactionSuccessful()
|
||||
return v1.recipientId
|
||||
}
|
||||
}
|
||||
|
||||
val id = getOrInsertFromGroupId(groupId)
|
||||
db.setTransactionSuccessful()
|
||||
return id
|
||||
|
||||
@@ -5,9 +5,9 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.storageservice.protos.groups.AccessControl
|
||||
import org.signal.storageservice.protos.groups.local.EnabledState
|
||||
import org.thoughtcrime.securesms.database.GroupTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.groups.GroupAccessControl
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
@@ -134,7 +134,7 @@ class GroupRecord(
|
||||
unmigratedV1Members
|
||||
.filterNot { members.contains(it) }
|
||||
.map { Recipient.resolved(it) }
|
||||
.filter { GroupsV1MigrationUtil.isAutoMigratable(it) }
|
||||
.filter { it.isAutoMigratable() }
|
||||
.map { it.id }
|
||||
}
|
||||
}
|
||||
@@ -181,4 +181,13 @@ class GroupRecord(
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* True if the user meets all the requirements to be auto-migrated, otherwise false.
|
||||
*/
|
||||
private fun Recipient.isAutoMigratable(): Boolean {
|
||||
return hasServiceId() && registered === RecipientTable.RegisteredState.REGISTERED && profileKey != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,13 +937,6 @@ final class GroupManagerV2 {
|
||||
alreadyAMember = true;
|
||||
}
|
||||
|
||||
GroupRecord unmigratedV1Group = GroupsV1MigratedCache.getV1GroupByV2Id(groupId);
|
||||
|
||||
if (unmigratedV1Group != null) {
|
||||
Log.i(TAG, "Group link was for a migrated V1 group we know about! Migrating it and using that as the base.");
|
||||
GroupsV1MigrationUtil.performLocalMigration(context, unmigratedV1Group.getId().requireV1());
|
||||
}
|
||||
|
||||
DecryptedGroup decryptedGroup = createPlaceholderGroup(joinInfo, requestToJoin);
|
||||
|
||||
Optional<GroupRecord> group = groupDatabase.getGroup(groupId);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.groups
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.util.LRUCache
|
||||
|
||||
/**
|
||||
* Cache to keep track of groups we know do not need a migration run on. This is to save time looking for a gv1 group
|
||||
* with the expected v2 id.
|
||||
*/
|
||||
object GroupsV1MigratedCache {
|
||||
private const val MAX_CACHE = 1000
|
||||
|
||||
private val noV1GroupCache = LRUCache<GroupId.V2, Boolean>(MAX_CACHE)
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun hasV1Group(groupId: GroupId.V2): Boolean {
|
||||
return getV1GroupByV2Id(groupId) != null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun getV1GroupByV2Id(groupId: GroupId.V2): GroupRecord? {
|
||||
synchronized(noV1GroupCache) {
|
||||
if (noV1GroupCache.containsKey(groupId)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val v1Group = SignalDatabase.groups.getGroupV1ByExpectedV2(groupId)
|
||||
if (!v1Group.isPresent) {
|
||||
synchronized(noV1GroupCache) {
|
||||
noV1GroupCache.put(groupId, true)
|
||||
}
|
||||
}
|
||||
return v1Group.orNull()
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
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.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor.LATEST;
|
||||
|
||||
public final class GroupsV1MigrationUtil {
|
||||
|
||||
private static final String TAG = Log.tag(GroupsV1MigrationUtil.class);
|
||||
|
||||
private GroupsV1MigrationUtil() {}
|
||||
|
||||
public static void migrate(@NonNull Context context, @NonNull RecipientId recipientId, boolean forced)
|
||||
throws IOException, RetryLaterException, GroupChangeBusyException, InvalidMigrationStateException
|
||||
{
|
||||
Recipient groupRecipient = Recipient.resolved(recipientId);
|
||||
Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
|
||||
GroupTable groupDatabase = SignalDatabase.groups();
|
||||
|
||||
if (threadId == null) {
|
||||
Log.w(TAG, "No thread found!");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
if (!groupRecipient.isPushV1Group()) {
|
||||
Log.w(TAG, "Not a V1 group!");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
if (groupRecipient.getParticipantIds().size() > FeatureFlags.groupLimits().getHardLimit()) {
|
||||
Log.w(TAG, "Too many members! Size: " + groupRecipient.getParticipantIds().size());
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
GroupId.V1 gv1Id = groupRecipient.requireGroupId().requireV1();
|
||||
GroupId.V2 gv2Id = gv1Id.deriveV2MigrationGroupId();
|
||||
GroupMasterKey gv2MasterKey = gv1Id.deriveV2MigrationMasterKey();
|
||||
boolean newlyCreated = false;
|
||||
|
||||
if (groupDatabase.groupExists(gv2Id)) {
|
||||
Log.w(TAG, "We already have a V2 group for this V1 group! Must have been added before we were migration-capable.");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
if (!groupRecipient.isActiveGroup()) {
|
||||
Log.w(TAG, "Group is inactive! Can't migrate.");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
switch (GroupManager.v2GroupStatus(context, SignalStore.account().requireAci(), gv2MasterKey)) {
|
||||
case DOES_NOT_EXIST:
|
||||
Log.i(TAG, "Group does not exist on the service.");
|
||||
|
||||
if (!groupRecipient.isProfileSharing()) {
|
||||
Log.w(TAG, "Profile sharing is disabled! Can't migrate.");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
List<Recipient> registeredMembers = RecipientUtil.getEligibleForSending(Recipient.resolvedList(groupRecipient.getParticipantIds()));
|
||||
|
||||
if (RecipientUtil.ensureUuidsAreAvailable(context, registeredMembers)) {
|
||||
Log.i(TAG, "Newly-discovered UUIDs. Getting fresh recipients.");
|
||||
registeredMembers = Stream.of(registeredMembers).map(Recipient::fresh).toList();
|
||||
}
|
||||
|
||||
List<Recipient> possibleMembers = forced ? registeredMembers
|
||||
: getMigratableAutoMigrationMembers(registeredMembers);
|
||||
|
||||
if (!forced && !groupRecipient.hasName()) {
|
||||
Log.w(TAG, "Group has no name. Skipping auto-migration.");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
if (!forced && possibleMembers.size() != registeredMembers.size()) {
|
||||
Log.w(TAG, "Not allowed to invite or leave registered users behind in an auto-migration! Skipping.");
|
||||
throw new InvalidMigrationStateException();
|
||||
}
|
||||
|
||||
Log.i(TAG, "Attempting to create group.");
|
||||
|
||||
try {
|
||||
GroupManager.migrateGroupToServer(context, gv1Id, possibleMembers);
|
||||
newlyCreated = true;
|
||||
Log.i(TAG, "Successfully created!");
|
||||
} catch (GroupChangeFailedException e) {
|
||||
Log.w(TAG, "Failed to migrate group. Retrying.", e);
|
||||
throw new RetryLaterException();
|
||||
} catch (MembershipNotSuitableForV2Exception e) {
|
||||
Log.w(TAG, "Failed to migrate job due to the membership not yet being suitable for GV2. Aborting.", e);
|
||||
return;
|
||||
} catch (GroupAlreadyExistsException e) {
|
||||
Log.w(TAG, "Someone else created the group while we were trying to do the same! It exists now. Continuing on.", e);
|
||||
}
|
||||
break;
|
||||
case NOT_A_MEMBER:
|
||||
Log.w(TAG, "The migrated group already exists, but we are not a member. Doing a local leave.");
|
||||
handleLeftBehind(gv1Id);
|
||||
return;
|
||||
case FULL_OR_PENDING_MEMBER:
|
||||
Log.w(TAG, "The migrated group already exists, and we're in it. Continuing on.");
|
||||
break;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
|
||||
Log.i(TAG, "Migrating local group " + gv1Id + " to " + gv2Id);
|
||||
|
||||
DecryptedGroup decryptedGroup = performLocalMigration(context, gv1Id, threadId, groupRecipient);
|
||||
|
||||
if (newlyCreated && decryptedGroup != null) {
|
||||
Log.i(TAG, "Sending no-op update to notify others.");
|
||||
GroupManager.sendNoopUpdate(context, gv2MasterKey, decryptedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
public static void performLocalMigration(@NonNull Context context, @NonNull GroupId.V1 gv1Id) throws IOException
|
||||
{
|
||||
Log.i(TAG, "Beginning local migration! V1 ID: " + gv1Id, new Throwable());
|
||||
try (Closeable ignored = GroupsV2ProcessingLock.acquireGroupProcessingLock(1000)) {
|
||||
if (SignalDatabase.groups().groupExists(gv1Id.deriveV2MigrationGroupId())) {
|
||||
Log.w(TAG, "Group was already migrated! Could have been waiting for the lock.", new Throwable());
|
||||
return;
|
||||
}
|
||||
|
||||
Recipient recipient = Recipient.externalGroupExact(gv1Id);
|
||||
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
|
||||
|
||||
performLocalMigration(context, gv1Id, threadId, recipient);
|
||||
Log.i(TAG, "Migration complete! (" + gv1Id + ", " + threadId + ", " + recipient.getId() + ")", new Throwable());
|
||||
} catch (GroupChangeBusyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable DecryptedGroup performLocalMigration(@NonNull Context context,
|
||||
@NonNull GroupId.V1 gv1Id,
|
||||
long threadId,
|
||||
@NonNull Recipient groupRecipient)
|
||||
throws IOException, GroupChangeBusyException
|
||||
{
|
||||
Log.i(TAG, "performLocalMigration(" + gv1Id + ", " + threadId + ", " + groupRecipient.getId());
|
||||
|
||||
try (Closeable ignored = GroupsV2ProcessingLock.acquireGroupProcessingLock()){
|
||||
DecryptedGroup decryptedGroup;
|
||||
try {
|
||||
decryptedGroup = GroupManager.addedGroupVersion(SignalStore.account().requireAci(), context, gv1Id.deriveV2MigrationMasterKey());
|
||||
} catch (GroupDoesNotExistException e) {
|
||||
throw new IOException("[Local] The group should exist already!");
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "[Local] We are not in the group. Doing a local leave.");
|
||||
handleLeftBehind(gv1Id);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.revision);
|
||||
SignalDatabase.groups().migrateToV2(threadId, gv1Id, decryptedGroup);
|
||||
|
||||
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.revision);
|
||||
try {
|
||||
GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null);
|
||||
} catch (GroupChangeBusyException | GroupNotAMemberException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
return decryptedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleLeftBehind(@NonNull GroupId.V1 gv1Id) {
|
||||
SignalDatabase.groups().setActive(gv1Id, false);
|
||||
SignalDatabase.groups().remove(gv1Id, Recipient.self().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* In addition to meeting traditional requirements, you must also have a profile key for a member
|
||||
* to consider them migratable in an auto-migration.
|
||||
*/
|
||||
private static @NonNull List<Recipient> getMigratableAutoMigrationMembers(@NonNull List<Recipient> registeredMembers) {
|
||||
return Stream.of(registeredMembers)
|
||||
.filter(r -> r.getProfileKey() != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the user meets all the requirements to be auto-migrated, otherwise false.
|
||||
*/
|
||||
public static boolean isAutoMigratable(@NonNull Recipient recipient) {
|
||||
return recipient.hasServiceId() &&
|
||||
recipient.getRegistered() == RecipientTable.RegisteredState.REGISTERED &&
|
||||
recipient.getProfileKey() != null;
|
||||
}
|
||||
|
||||
public static final class InvalidMigrationStateException extends Exception {
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,7 @@ import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Shows more info about a GV1->GV2 migration event. Looks similar to
|
||||
* {@link GroupsV1MigrationInitiationBottomSheetDialogFragment}, but only displays static data.
|
||||
* Shows more info about a GV1->GV2 migration event.
|
||||
*/
|
||||
public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
/**
|
||||
* A bottom sheet that allows a user to initiation a manual GV1->GV2 migration. Will show the user
|
||||
* the members that will be invited/left behind.
|
||||
*/
|
||||
public final class GroupsV1MigrationInitiationBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
|
||||
private static final String KEY_GROUP_RECIPIENT_ID = "group_recipient_id";
|
||||
|
||||
private GroupsV1MigrationInitiationViewModel viewModel;
|
||||
private GroupMemberListView inviteList;
|
||||
private TextView inviteTitle;
|
||||
private View inviteContainer;
|
||||
private GroupMemberListView ineligibleList;
|
||||
private TextView ineligibleTitle;
|
||||
private View ineligibleContainer;
|
||||
private View upgradeButton;
|
||||
private View spinner;
|
||||
|
||||
public static void showForInitiation(@NonNull FragmentManager manager, @NonNull RecipientId groupRecipientId) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(KEY_GROUP_RECIPIENT_ID, groupRecipientId);
|
||||
|
||||
GroupsV1MigrationInitiationBottomSheetDialogFragment fragment = new GroupsV1MigrationInitiationBottomSheetDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
|
||||
fragment.show(manager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setStyle(DialogFragment.STYLE_NORMAL,
|
||||
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_RoundedBottomSheet
|
||||
: R.style.Theme_Signal_RoundedBottomSheet_Light);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.groupsv1_migration_bottom_sheet, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.inviteContainer = view.findViewById(R.id.gv1_migrate_invite_container);
|
||||
this.inviteTitle = view.findViewById(R.id.gv1_migrate_invite_title);
|
||||
this.inviteList = view.findViewById(R.id.gv1_migrate_invite_list);
|
||||
this.ineligibleContainer = view.findViewById(R.id.gv1_migrate_ineligible_container);
|
||||
this.ineligibleTitle = view.findViewById(R.id.gv1_migrate_ineligible_title);
|
||||
this.ineligibleList = view.findViewById(R.id.gv1_migrate_ineligible_list);
|
||||
this.upgradeButton = view.findViewById(R.id.gv1_migrate_upgrade_button);
|
||||
this.spinner = view.findViewById(R.id.gv1_migrate_spinner);
|
||||
|
||||
inviteList.initializeAdapter(getViewLifecycleOwner());
|
||||
ineligibleList.initializeAdapter(getViewLifecycleOwner());
|
||||
|
||||
inviteList.setNestedScrollingEnabled(false);
|
||||
ineligibleList.setNestedScrollingEnabled(false);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
RecipientId groupRecipientId = getArguments().getParcelable(KEY_GROUP_RECIPIENT_ID);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
viewModel = new ViewModelProvider(this, new GroupsV1MigrationInitiationViewModel.Factory(groupRecipientId)).get(GroupsV1MigrationInitiationViewModel.class);
|
||||
viewModel.getMigrationState().observe(getViewLifecycleOwner(), this::onMigrationStateChanged);
|
||||
|
||||
upgradeButton.setEnabled(false);
|
||||
upgradeButton.setOnClickListener(v -> onUpgradeClicked());
|
||||
view.findViewById(R.id.gv1_migrate_cancel_button).setOnClickListener(v -> dismiss());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
WindowUtil.initializeScreenshotSecurity(requireContext(), requireDialog().getWindow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
|
||||
BottomSheetUtil.show(manager, tag, this);
|
||||
}
|
||||
|
||||
private void onMigrationStateChanged(@NonNull MigrationState migrationState) {
|
||||
if (migrationState.getNeedsInvite().size() > 0) {
|
||||
inviteContainer.setVisibility(View.VISIBLE);
|
||||
inviteTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationInitiation_these_members_will_need_to_accept_an_invite, migrationState.getNeedsInvite().size()));
|
||||
inviteList.setDisplayOnlyMembers(migrationState.getNeedsInvite());
|
||||
} else {
|
||||
inviteContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (migrationState.getIneligible().size() > 0) {
|
||||
ineligibleContainer.setVisibility(View.VISIBLE);
|
||||
ineligibleTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationInitiation_these_members_are_not_capable_of_joining_new_groups, migrationState.getIneligible().size()));
|
||||
ineligibleList.setDisplayOnlyMembers(migrationState.getIneligible());
|
||||
} else {
|
||||
ineligibleContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
upgradeButton.setEnabled(true);
|
||||
spinner.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void onUpgradeClicked() {
|
||||
AlertDialog dialog = SimpleProgressDialog.show(requireContext());
|
||||
viewModel.onUpgradeClicked().observe(getViewLifecycleOwner(), result -> {
|
||||
switch (result) {
|
||||
case SUCCESS:
|
||||
dismiss();
|
||||
break;
|
||||
case FAILURE_GENERAL:
|
||||
Toast.makeText(requireContext(), R.string.GroupsV1MigrationInitiation_failed_to_upgrade, Toast.LENGTH_SHORT).show();
|
||||
dismiss();
|
||||
break;
|
||||
case FAILURE_NETWORK:
|
||||
Toast.makeText(requireContext(), R.string.GroupsV1MigrationInitiation_encountered_a_network_error, Toast.LENGTH_SHORT).show();
|
||||
dismiss();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
class GroupsV1MigrationInitiationViewModel extends ViewModel {
|
||||
|
||||
private final RecipientId groupRecipientId;
|
||||
private final MutableLiveData<MigrationState> migrationState;
|
||||
private final GroupsV1MigrationRepository repository;
|
||||
|
||||
private GroupsV1MigrationInitiationViewModel(@NonNull RecipientId groupRecipientId) {
|
||||
this.groupRecipientId = groupRecipientId;
|
||||
this.migrationState = new MutableLiveData<>();
|
||||
this.repository = new GroupsV1MigrationRepository();
|
||||
|
||||
repository.getMigrationState(groupRecipientId, migrationState::postValue);
|
||||
}
|
||||
|
||||
@NonNull LiveData<MigrationState> getMigrationState() {
|
||||
return migrationState;
|
||||
}
|
||||
|
||||
@NonNull LiveData<MigrationResult> onUpgradeClicked() {
|
||||
MutableLiveData <MigrationResult> migrationResult = new MutableLiveData<>();
|
||||
|
||||
repository.upgradeGroup(groupRecipientId, migrationResult::postValue);
|
||||
|
||||
return migrationResult;
|
||||
}
|
||||
|
||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||
|
||||
private final RecipientId groupRecipientId;
|
||||
|
||||
Factory(@NonNull RecipientId groupRecipientId) {
|
||||
this.groupRecipientId = groupRecipientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return modelClass.cast(new GroupsV1MigrationInitiationViewModel(groupRecipientId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
final class GroupsV1MigrationRepository {
|
||||
|
||||
private static final String TAG = Log.tag(GroupsV1MigrationRepository.class);
|
||||
|
||||
void getMigrationState(@NonNull RecipientId groupRecipientId, @NonNull Consumer<MigrationState> callback) {
|
||||
SignalExecutors.BOUNDED.execute(() -> callback.accept(getMigrationState(groupRecipientId)));
|
||||
}
|
||||
|
||||
void upgradeGroup(@NonNull RecipientId recipientId, @NonNull Consumer<MigrationResult> callback) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
if (!NetworkConstraint.isMet(ApplicationDependencies.getApplication())) {
|
||||
Log.w(TAG, "No network!");
|
||||
callback.accept(MigrationResult.FAILURE_NETWORK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Recipient.resolved(recipientId).isPushV1Group()) {
|
||||
Log.w(TAG, "Not a V1 group!");
|
||||
callback.accept(MigrationResult.FAILURE_GENERAL);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
GroupsV1MigrationUtil.migrate(ApplicationDependencies.getApplication(), recipientId, true);
|
||||
callback.accept(MigrationResult.SUCCESS);
|
||||
} catch (IOException | RetryLaterException | GroupChangeBusyException e) {
|
||||
callback.accept(MigrationResult.FAILURE_NETWORK);
|
||||
} catch (GroupsV1MigrationUtil.InvalidMigrationStateException e) {
|
||||
callback.accept(MigrationResult.FAILURE_GENERAL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private MigrationState getMigrationState(@NonNull RecipientId groupRecipientId) {
|
||||
Recipient group = Recipient.resolved(groupRecipientId);
|
||||
|
||||
if (!group.isPushV1Group()) {
|
||||
return new MigrationState(Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
List<Recipient> members = Recipient.resolvedList(group.getParticipantIds());
|
||||
|
||||
try {
|
||||
List<Recipient> registered = Stream.of(members)
|
||||
.filter(Recipient::isRegistered)
|
||||
.toList();
|
||||
|
||||
RecipientUtil.ensureUuidsAreAvailable(ApplicationDependencies.getApplication(), registered);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to refresh UUIDs!", e);
|
||||
}
|
||||
|
||||
group = group.fresh();
|
||||
|
||||
List<Recipient> ineligible = Stream.of(members)
|
||||
.filter(r -> !r.hasServiceId() || r.getRegistered() != RecipientTable.RegisteredState.REGISTERED)
|
||||
.toList();
|
||||
|
||||
List<Recipient> invites = Stream.of(members)
|
||||
.filterNot(ineligible::contains)
|
||||
.filterNot(Recipient::isSelf)
|
||||
.filter(r -> r.getProfileKey() == null)
|
||||
.toList();
|
||||
|
||||
return new MigrationState(invites, ineligible);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||
|
||||
enum MigrationResult {
|
||||
SUCCESS, FAILURE_GENERAL, FAILURE_NETWORK
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the migration state of a group. Namely, which users will be invited or left behind.
|
||||
*/
|
||||
final class MigrationState {
|
||||
private final List<Recipient> needsInvite;
|
||||
private final List<Recipient> ineligible;
|
||||
|
||||
MigrationState(@NonNull List<Recipient> needsInvite,
|
||||
@NonNull List<Recipient> ineligible)
|
||||
{
|
||||
this.needsInvite = needsInvite;
|
||||
this.ineligible = ineligible;
|
||||
}
|
||||
|
||||
public @NonNull List<Recipient> getNeedsInvite() {
|
||||
return needsInvite;
|
||||
}
|
||||
|
||||
public @NonNull List<Recipient> getIneligible() {
|
||||
return ineligible;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GroupV1MigrationJob extends BaseJob {
|
||||
|
||||
private static final String TAG = Log.tag(GroupV1MigrationJob.class);
|
||||
|
||||
public static final String KEY = "GroupV1MigrationJob";
|
||||
|
||||
private static final String KEY_RECIPIENT_ID = "recipient_id";
|
||||
|
||||
private static final int ROUTINE_LIMIT = 20;
|
||||
private static final long REFRESH_INTERVAL = TimeUnit.HOURS.toMillis(1);
|
||||
|
||||
private final RecipientId recipientId;
|
||||
|
||||
private GroupV1MigrationJob(@NonNull RecipientId recipientId) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(recipientId.toQueueKey())
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(7))
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build(),
|
||||
recipientId);
|
||||
}
|
||||
|
||||
private GroupV1MigrationJob(@NonNull Parameters parameters, @NonNull RecipientId recipientId) {
|
||||
super(parameters);
|
||||
this.recipientId = recipientId;
|
||||
}
|
||||
|
||||
public static void enqueuePossibleAutoMigrate(@NonNull RecipientId recipientId) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
if (Recipient.resolved(recipientId).isPushV1Group()) {
|
||||
ApplicationDependencies.getJobManager().add(new GroupV1MigrationJob(recipientId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return new JsonJobData.Builder().putString(KEY_RECIPIENT_ID, recipientId.serialize())
|
||||
.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, GroupChangeBusyException, RetryLaterException {
|
||||
if (Recipient.resolved(recipientId).isBlocked()) {
|
||||
Log.i(TAG, "Group blocked. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
GroupsV1MigrationUtil.migrate(context, recipientId, false);
|
||||
} catch (GroupsV1MigrationUtil.InvalidMigrationStateException e) {
|
||||
Log.w(TAG, "Invalid migration state. Skipping.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof PushNetworkException ||
|
||||
e instanceof NoCredentialForRedemptionTimeException ||
|
||||
e instanceof GroupChangeBusyException ||
|
||||
e instanceof RetryLaterException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<GroupV1MigrationJob> {
|
||||
@Override
|
||||
public @NonNull GroupV1MigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
return new GroupV1MigrationJob(parameters, RecipientId.from(data.getString(KEY_RECIPIENT_ID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,6 @@ public final class JobManagerFactories {
|
||||
put(ForceUpdateGroupV2WorkerJob.KEY, new ForceUpdateGroupV2WorkerJob.Factory());
|
||||
put(GenerateAudioWaveFormJob.KEY, new GenerateAudioWaveFormJob.Factory());
|
||||
put(GiftSendJob.KEY, new GiftSendJob.Factory());
|
||||
put(GroupV1MigrationJob.KEY, new GroupV1MigrationJob.Factory());
|
||||
put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory());
|
||||
put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory());
|
||||
put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory());
|
||||
@@ -305,6 +304,7 @@ public final class JobManagerFactories {
|
||||
put("MmsDownloadJob", new FailingJob.Factory());
|
||||
put("SmsReceiveJob", new FailingJob.Factory());
|
||||
put("StoryReadStateMigrationJob", new PassingMigrationJob.Factory());
|
||||
put("GroupV1MigrationJob", new FailingJob.Factory());
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.ChangeNumberConstraint
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
@@ -122,7 +121,7 @@ class PushProcessMessageJob private constructor(
|
||||
if (groupId.isV2) {
|
||||
val localRevision = groups.getGroupV2Revision(groupId.requireV2())
|
||||
|
||||
if (groupContext.revision!! > localRevision || GroupsV1MigratedCache.hasV1Group(groupId)) {
|
||||
if (groupContext.revision!! > localRevision) {
|
||||
Log.i(TAG, "Adding network constraint to group-related job.")
|
||||
requireNetwork = true
|
||||
}
|
||||
|
||||
@@ -126,21 +126,17 @@ public final class MessageRequestRepository {
|
||||
}
|
||||
} else if (!RecipientUtil.isLegacyProfileSharingAccepted(recipient) && isLegacyThread(recipient)) {
|
||||
if (recipient.isGroup()) {
|
||||
return MessageRequestState.LEGACY_GROUP_V1;
|
||||
return MessageRequestState.DEPRECATED_GROUP_V1;
|
||||
} else {
|
||||
return MessageRequestState.LEGACY_INDIVIDUAL;
|
||||
}
|
||||
} else if (recipient.isPushV1Group()) {
|
||||
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
||||
if (recipient.getParticipantIds().size() > FeatureFlags.groupLimits().getHardLimit()) {
|
||||
return MessageRequestState.DEPRECATED_GROUP_V1_TOO_LARGE;
|
||||
} else {
|
||||
return MessageRequestState.DEPRECATED_GROUP_V1;
|
||||
}
|
||||
return MessageRequestState.DEPRECATED_GROUP_V1;
|
||||
} else if (!recipient.isActiveGroup()) {
|
||||
return MessageRequestState.NONE;
|
||||
} else {
|
||||
return MessageRequestState.GROUP_V1;
|
||||
return MessageRequestState.DEPRECATED_GROUP_V1;
|
||||
}
|
||||
} else {
|
||||
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
||||
|
||||
@@ -19,18 +19,9 @@ public enum MessageRequestState {
|
||||
/** An individual conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
||||
LEGACY_INDIVIDUAL,
|
||||
|
||||
/** A V1 group conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
||||
LEGACY_GROUP_V1,
|
||||
|
||||
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on. */
|
||||
DEPRECATED_GROUP_V1,
|
||||
|
||||
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on, but it's also too large to migrate. Nothing we can do. */
|
||||
DEPRECATED_GROUP_V1_TOO_LARGE,
|
||||
|
||||
/** A message request is needed for a V1 group */
|
||||
GROUP_V1,
|
||||
|
||||
/** An invite response is needed for a V2 group */
|
||||
GROUP_V2_INVITE,
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
@@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.HtmlUtil;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
|
||||
@@ -29,7 +28,6 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
|
||||
private LearnMoreTextView question;
|
||||
private MaterialButton accept;
|
||||
private MaterialButton gv1Continue;
|
||||
private MaterialButton block;
|
||||
private MaterialButton delete;
|
||||
private MaterialButton bigDelete;
|
||||
@@ -38,8 +36,7 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
|
||||
private Group normalButtons;
|
||||
private Group blockedButtons;
|
||||
private Group gv1MigrationButtons;
|
||||
private Group activeGroup;
|
||||
private @Nullable Group activeGroup;
|
||||
|
||||
public MessageRequestsBottomView(Context context) {
|
||||
super(context);
|
||||
@@ -66,10 +63,8 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
delete = findViewById(R.id.message_request_delete);
|
||||
bigDelete = findViewById(R.id.message_request_big_delete);
|
||||
bigUnblock = findViewById(R.id.message_request_big_unblock);
|
||||
gv1Continue = findViewById(R.id.message_request_gv1_migration);
|
||||
normalButtons = findViewById(R.id.message_request_normal_buttons);
|
||||
blockedButtons = findViewById(R.id.message_request_blocked_buttons);
|
||||
gv1MigrationButtons = findViewById(R.id.message_request_gv1_migration_buttons);
|
||||
busyIndicator = findViewById(R.id.message_request_busy_indicator);
|
||||
|
||||
setWallpaperEnabled(false);
|
||||
@@ -89,67 +84,49 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
|
||||
question.setText(HtmlCompat.fromHtml(getContext().getString(message,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0));
|
||||
setActiveInactiveGroups(blockedButtons, normalButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(blockedButtons, normalButtons);
|
||||
break;
|
||||
case BLOCKED_GROUP:
|
||||
question.setText(R.string.MessageRequestBottomView_unblock_this_group_and_share_your_name_and_photo_with_its_members);
|
||||
setActiveInactiveGroups(blockedButtons, normalButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(blockedButtons, normalButtons);
|
||||
break;
|
||||
case LEGACY_INDIVIDUAL:
|
||||
question.setText(getContext().getString(R.string.MessageRequestBottomView_continue_your_conversation_with_s_and_share_your_name_and_photo, recipient.getShortDisplayName(getContext())));
|
||||
question.setLearnMoreVisible(true);
|
||||
question.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(getContext(), getContext().getString(R.string.MessageRequestBottomView_legacy_learn_more_url)));
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_continue);
|
||||
break;
|
||||
case LEGACY_GROUP_V1:
|
||||
question.setText(R.string.MessageRequestBottomView_continue_your_conversation_with_this_group_and_share_your_name_and_photo);
|
||||
question.setLearnMoreVisible(true);
|
||||
question.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(getContext(), getContext().getString(R.string.MessageRequestBottomView_legacy_learn_more_url)));
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_continue);
|
||||
break;
|
||||
case DEPRECATED_GROUP_V1:
|
||||
question.setText(R.string.MessageRequestBottomView_upgrade_this_group_to_activate_new_features);
|
||||
setActiveInactiveGroups(gv1MigrationButtons, normalButtons, blockedButtons);
|
||||
gv1Continue.setVisibility(VISIBLE);
|
||||
break;
|
||||
case DEPRECATED_GROUP_V1_TOO_LARGE:
|
||||
question.setText(getContext().getString(R.string.MessageRequestBottomView_this_legacy_group_can_no_longer_be_used, FeatureFlags.groupLimits().getHardLimit() - 1));
|
||||
setActiveInactiveGroups(gv1MigrationButtons, normalButtons, blockedButtons);
|
||||
gv1Continue.setVisibility(GONE);
|
||||
break;
|
||||
case GROUP_V1:
|
||||
question.setText(R.string.MessageRequestBottomView_do_you_want_to_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_accept);
|
||||
setActiveInactiveGroups(null, normalButtons, blockedButtons);
|
||||
break;
|
||||
case GROUP_V2_INVITE:
|
||||
question.setText(R.string.MessageRequestBottomView_do_you_want_to_join_this_group_you_wont_see_their_messages);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_accept);
|
||||
break;
|
||||
case GROUP_V2_ADD:
|
||||
question.setText(R.string.MessageRequestBottomView_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_accept);
|
||||
break;
|
||||
case INDIVIDUAL:
|
||||
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0));
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_accept);
|
||||
break;
|
||||
case INDIVIDUAL_HIDDEN:
|
||||
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_you_removed_them_before,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0));
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons);
|
||||
setActiveInactiveGroups(normalButtons, blockedButtons);
|
||||
accept.setText(R.string.MessageRequestBottomView_accept);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setActiveInactiveGroups(@NonNull Group activeGroup, @NonNull Group... inActiveGroups) {
|
||||
private void setActiveInactiveGroups(@Nullable Group activeGroup, @NonNull Group... inActiveGroups) {
|
||||
int initialVisibility = this.activeGroup != null ? this.activeGroup.getVisibility() : VISIBLE;
|
||||
|
||||
this.activeGroup = activeGroup;
|
||||
@@ -158,7 +135,9 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
inactive.setVisibility(GONE);
|
||||
}
|
||||
|
||||
activeGroup.setVisibility(initialVisibility);
|
||||
if (activeGroup != null) {
|
||||
activeGroup.setVisibility(initialVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
public void showBusy() {
|
||||
@@ -179,7 +158,7 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
public void setWallpaperEnabled(boolean isEnabled) {
|
||||
MessageRequestBarColorTheme theme = MessageRequestBarColorTheme.resolveTheme(isEnabled);
|
||||
|
||||
Stream.of(delete, bigDelete, block, bigUnblock, accept, gv1Continue).forEach(button -> {
|
||||
Stream.of(delete, bigDelete, block, bigUnblock, accept).forEach(button -> {
|
||||
button.setBackgroundTintList(ColorStateList.valueOf(theme.getButtonBackgroundColor(getContext())));
|
||||
});
|
||||
|
||||
@@ -187,7 +166,7 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
button.setTextColor(theme.getButtonForegroundDenyColor(getContext()));
|
||||
});
|
||||
|
||||
Stream.of(accept, bigUnblock, gv1Continue).forEach(button -> {
|
||||
Stream.of(accept, bigUnblock).forEach(button -> {
|
||||
button.setTextColor(theme.getButtonForegroundAcceptColor(getContext()));
|
||||
});
|
||||
|
||||
@@ -210,8 +189,4 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||
public void setUnblockOnClickListener(OnClickListener unblockOnClickListener) {
|
||||
bigUnblock.setOnClickListener(unblockOnClickListener);
|
||||
}
|
||||
|
||||
public void setGroupV1MigrationContinueListener(OnClickListener acceptOnClickListener) {
|
||||
gv1Continue.setOnClickListener(acceptOnClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import org.thoughtcrime.securesms.groups.GroupChangeBusyException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.thoughtcrime.securesms.groups.GroupNotAMemberException
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigratedCache
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob
|
||||
import org.thoughtcrime.securesms.jobs.NullMessageSendJob
|
||||
@@ -232,10 +230,6 @@ open class MessageContentProcessor(private val context: Context) {
|
||||
senderRecipient: Recipient,
|
||||
groupSecretParams: GroupSecretParams? = null
|
||||
): Gv2PreProcessResult {
|
||||
val v1Group = GroupsV1MigratedCache.getV1GroupByV2Id(groupId)
|
||||
if (v1Group != null) {
|
||||
GroupsV1MigrationUtil.performLocalMigration(context, v1Group.id.requireV1())
|
||||
}
|
||||
val preUpdateGroupRecord = SignalDatabase.groups.getGroup(groupId)
|
||||
val groupUpdateResult = updateGv2GroupFromServerOrP2PChange(context, timestamp, groupV2, preUpdateGroupRecord, groupSecretParams)
|
||||
if (groupUpdateResult == null) {
|
||||
|
||||
@@ -11,12 +11,10 @@ import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -98,26 +96,9 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This contains a pretty big compromise: In the event that the new GV2 group we learned about
|
||||
* was, in fact, a migrated V1 group we already knew about, we handle the migration here. This
|
||||
* isn't great because the migration will likely result in network activity. And because this is
|
||||
* all happening in a transaction, this could keep the transaction open for longer than we'd like.
|
||||
* However, given that nearly all V1 groups have already been migrated, we're at a point where
|
||||
* this event should be extraordinarily rare, and it didn't seem worth it to add a lot of
|
||||
* complexity to accommodate this specific scenario.
|
||||
*/
|
||||
@Override
|
||||
void insertLocal(@NonNull SignalGroupV2Record record) throws IOException {
|
||||
GroupId.V2 actualV2Id = GroupId.v2(record.getMasterKeyOrThrow());
|
||||
GroupId.V1 possibleV1Id = gv1GroupsByExpectedGv2Id.get(actualV2Id);
|
||||
|
||||
if (possibleV1Id != null) {
|
||||
Log.i(TAG, "Discovered a new GV2 ID that is actually a migrated V1 group! Migrating now.");
|
||||
GroupsV1MigrationUtil.performLocalMigration(context, possibleV1Id);
|
||||
} else {
|
||||
recipientTable.applyStorageSyncGroupV2Insert(record);
|
||||
}
|
||||
void insertLocal(@NonNull SignalGroupV2Record record) {
|
||||
recipientTable.applyStorageSyncGroupV2Insert(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -66,19 +66,6 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/message_request_delete"
|
||||
app:layout_constraintTop_toTopOf="@id/message_request_block" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/message_request_gv1_migration"
|
||||
style="@style/Signal.Widget.Button.Medium.Tonal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/MessageRequestBottomView_continue"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/message_request_big_delete"
|
||||
style="@style/Signal.Widget.Button.Medium.Tonal"
|
||||
@@ -109,7 +96,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="message_request_block,message_request_big_delete,message_request_gv1_migration" />
|
||||
app:constraint_referenced_ids="message_request_block,message_request_big_delete" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/message_request_normal_buttons"
|
||||
@@ -127,14 +114,6 @@
|
||||
app:constraint_referenced_ids="message_request_big_delete,message_request_big_unblock"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/message_request_gv1_migration_buttons"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="message_request_gv1_migration"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/message_request_busy_indicator"
|
||||
style="?android:attr/progressBarStyle"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:title="@string/conversation_group_options__delivery"
|
||||
android:id="@+id/menu_group_delivery"
|
||||
android:icon="@drawable/ic_call_split_white_24dp"
|
||||
app:iconTint="@color/signal_icon_tint_primary"
|
||||
app:showAsAction="ifRoom" >
|
||||
|
||||
<menu>
|
||||
<group android:id="@+id/distribution_group"
|
||||
android:checkableBehavior="single">
|
||||
<item android:id="@+id/menu_distribution_conversation" android:title="@string/conversation_group_options__conversation" android:checked="true" />
|
||||
<item android:id="@+id/menu_distribution_broadcast" android:title="@string/conversation_group_options__broadcast" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@@ -1535,7 +1535,7 @@
|
||||
<string name="MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them_SMS">Let %1$s message you? You won\'t receive any messages until you unblock them.</string>
|
||||
<string name="MessageRequestBottomView_get_updates_and_news_from_s_you_wont_receive_any_updates_until_you_unblock_them">Get updates and news from %1$s? You won\'t receive any updates until you unblock them.</string>
|
||||
<string name="MessageRequestBottomView_continue_your_conversation_with_this_group_and_share_your_name_and_photo">Continue your chat with this group and share your name and photo with its members?</string>
|
||||
<string name="MessageRequestBottomView_upgrade_this_group_to_activate_new_features">Upgrade this group to activate new features like @mentions and admins. Members who have not shared their name or photo in this group will be invited to join.</string>
|
||||
<string name="MessageRequestBottomView_upgrade_this_group_to_activate_new_features">This Legacy Group can no longer be used. Create a new group to activate new features like @mentions and admins.</string>
|
||||
<string name="MessageRequestBottomView_this_legacy_group_can_no_longer_be_used">This Legacy Group can no longer be used because it is too large. The maximum group size is %1$d.</string>
|
||||
<string name="MessageRequestBottomView_continue_your_conversation_with_s_and_share_your_name_and_photo">Continue your chat with %1$s and share your name and photo with them?</string>
|
||||
<string name="MessageRequestBottomView_do_you_want_to_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept">Join this group and share your name and photo with its members? They won\'t know you\'ve seen their messages until you accept.</string>
|
||||
|
||||
Reference in New Issue
Block a user