mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Fix blocking bugs for internal link and sync testing.
This commit is contained in:
@@ -44,7 +44,7 @@ class MessageHelper(private val harness: SignalActivityRule, var startTime: Long
|
||||
init {
|
||||
val threadIdSlot = slot<Long>()
|
||||
mockkStatic(ThreadUpdateJob::class)
|
||||
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
|
||||
every { ThreadUpdateJob.enqueue(capture(threadIdSlot), any()) } answers {
|
||||
SignalDatabase.threads.update(threadIdSlot.captured, false)
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class MessageHelper(private val harness: SignalActivityRule, var startTime: Long
|
||||
.groupChangeUpdate(GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account.getServiceIds(), decryptedGroupV2Context))
|
||||
.build()
|
||||
|
||||
val outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, startTime)
|
||||
val outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, startTime, false)
|
||||
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, null).messageId
|
||||
|
||||
@@ -490,7 +490,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
|
||||
}
|
||||
|
||||
private void ensureProfileUploaded() {
|
||||
if (SignalStore.account().isRegistered() && !SignalStore.registration().hasUploadedProfile() && !Recipient.self().getProfileName().isEmpty()) {
|
||||
if (SignalStore.account().isRegistered() && !SignalStore.registration().hasUploadedProfile() && !Recipient.self().getProfileName().isEmpty() && SignalStore.account().isPrimaryDevice()) {
|
||||
Log.w(TAG, "User has a profile, but has not uploaded one. Uploading now.");
|
||||
AppDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
}
|
||||
|
||||
@@ -195,7 +195,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
return !SignalStore.registration().isRegistrationComplete() &&
|
||||
!SignalStore.svr().hasPin() &&
|
||||
!SignalStore.svr().lastPinCreateFailed() &&
|
||||
!SignalStore.svr().hasOptedOut();
|
||||
!SignalStore.svr().hasOptedOut() &&
|
||||
SignalStore.account().isPrimaryDevice();
|
||||
}
|
||||
|
||||
private boolean userMustSetProfileName() {
|
||||
|
||||
@@ -290,68 +290,70 @@ private fun AppSettingsContent(
|
||||
BackupFailureState.NONE -> Unit
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.AccountSettingsFragment__account),
|
||||
icon = painterResource(R.drawable.symbol_person_circle_24),
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.AccountRoute.Account)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (state.isPrimaryDevice) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.AccountSettingsFragment__account),
|
||||
icon = painterResource(R.drawable.symbol_person_circle_24),
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.AccountRoute.Account)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.preferences__linked_devices),
|
||||
icon = painterResource(R.drawable.symbol_devices_24),
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.LinkDeviceRoute.LinkDevice)
|
||||
},
|
||||
enabled = isRegisteredAndUpToDate
|
||||
)
|
||||
}
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.preferences__linked_devices),
|
||||
icon = painterResource(R.drawable.symbol_devices_24),
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.LinkDeviceRoute.LinkDevice)
|
||||
},
|
||||
enabled = isRegisteredAndUpToDate
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val context = LocalContext.current
|
||||
val donateUrl = stringResource(R.string.donate_url)
|
||||
item {
|
||||
val context = LocalContext.current
|
||||
val donateUrl = stringResource(R.string.donate_url)
|
||||
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.preferences__donate_to_signal),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (state.hasExpiredGiftBadge) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_info_fill_24),
|
||||
tint = colorResource(R.color.signal_accent_primary),
|
||||
contentDescription = null
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.preferences__donate_to_signal),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_heart_24),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
if (state.allowUserToGoToDonationManagementScreen) {
|
||||
callbacks.navigate(AppSettingsRoute.DonationsRoute.Donations())
|
||||
} else {
|
||||
CommunicationActions.openBrowserLink(context, donateUrl)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
callbacks.copyDonorBadgeSubscriberIdToClipboard()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
if (state.hasExpiredGiftBadge) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_info_fill_24),
|
||||
tint = colorResource(R.color.signal_accent_primary),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_heart_24),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
if (state.allowUserToGoToDonationManagementScreen) {
|
||||
callbacks.navigate(AppSettingsRoute.DonationsRoute.Donations())
|
||||
} else {
|
||||
CommunicationActions.openBrowserLink(context, donateUrl)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
callbacks.copyDonorBadgeSubscriberIdToClipboard()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
@@ -408,29 +410,31 @@ private fun AppSettingsContent(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
TextWithBetaLabel(
|
||||
text = stringResource(R.string.preferences_chats__backups),
|
||||
textStyle = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_backup_24),
|
||||
contentDescription = stringResource(R.string.preferences_chats__backups),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.BackupsRoute.Backups)
|
||||
},
|
||||
onLongClick = {
|
||||
callbacks.copyRemoteBackupsSubscriberIdToClipboard()
|
||||
},
|
||||
enabled = isRegisteredAndUpToDate
|
||||
)
|
||||
if (state.isPrimaryDevice) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = {
|
||||
TextWithBetaLabel(
|
||||
text = stringResource(R.string.preferences_chats__backups),
|
||||
textStyle = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_backup_24),
|
||||
contentDescription = stringResource(R.string.preferences_chats__backups),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
callbacks.navigate(AppSettingsRoute.BackupsRoute.Backups)
|
||||
},
|
||||
onLongClick = {
|
||||
callbacks.copyRemoteBackupsSubscriberIdToClipboard()
|
||||
},
|
||||
enabled = isRegisteredAndUpToDate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
@@ -455,7 +459,7 @@ private fun AppSettingsContent(
|
||||
}
|
||||
}
|
||||
|
||||
if (state.showPayments) {
|
||||
if (state.isPrimaryDevice && state.showPayments) {
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
@@ -692,6 +696,7 @@ private fun AppSettingsContentPreview() {
|
||||
)
|
||||
),
|
||||
state = AppSettingsState(
|
||||
isPrimaryDevice = true,
|
||||
unreadPaymentsCount = 5,
|
||||
hasExpiredGiftBadge = true,
|
||||
allowUserToGoToDonationManagementScreen = true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
@Immutable
|
||||
data class AppSettingsState(
|
||||
val isPrimaryDevice: Boolean,
|
||||
val unreadPaymentsCount: Int,
|
||||
val hasExpiredGiftBadge: Boolean,
|
||||
val allowUserToGoToDonationManagementScreen: Boolean,
|
||||
|
||||
@@ -21,6 +21,7 @@ class AppSettingsViewModel : ViewModel() {
|
||||
|
||||
private val store = Store(
|
||||
AppSettingsState(
|
||||
isPrimaryDevice = SignalStore.account.isPrimaryDevice,
|
||||
unreadPaymentsCount = 0,
|
||||
hasExpiredGiftBadge = SignalStore.inAppPayments.getExpiredGiftBadge() != null,
|
||||
allowUserToGoToDonationManagementScreen = SignalStore.inAppPayments.isLikelyASustainer() || InAppDonations.hasAtLeastOnePaymentMethodAvailable(),
|
||||
|
||||
@@ -880,6 +880,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
|
||||
val groupRecipient = recipients.getOrInsertFromGroupId(groupId)
|
||||
Recipient.live(groupRecipient).refresh()
|
||||
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun remove(groupId: GroupId, source: RecipientId) {
|
||||
|
||||
@@ -2889,7 +2889,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
) {
|
||||
val incrementUnreadMentions = retrieved.mentions.isNotEmpty() && retrieved.mentions.any { it.recipientId == Recipient.self().id }
|
||||
threads.incrementUnread(threadId, 1, if (incrementUnreadMentions) 1 else 0)
|
||||
ThreadUpdateJob.enqueue(threadId)
|
||||
ThreadUpdateJob.enqueue(threadId, true)
|
||||
}
|
||||
|
||||
if (notifyObservers) {
|
||||
@@ -3362,7 +3362,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
if (!message.isIdentityVerified && !message.isIdentityDefault) {
|
||||
ThreadUpdateJob.enqueue(threadId)
|
||||
ThreadUpdateJob.enqueue(threadId, !message.isSelfGroupAdd)
|
||||
}
|
||||
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
|
||||
@@ -1582,20 +1582,20 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
|
||||
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalContactRecord) {
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread, isGroup = false)
|
||||
}
|
||||
|
||||
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalGroupV1Record) {
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread, isGroup = true)
|
||||
}
|
||||
|
||||
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalGroupV2Record) {
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
|
||||
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread, isGroup = true)
|
||||
}
|
||||
|
||||
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalAccountRecord) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
applyStorageSyncUpdate(recipientId, record.proto.noteToSelfArchived, record.proto.noteToSelfMarkedUnread)
|
||||
applyStorageSyncUpdate(recipientId, record.proto.noteToSelfArchived, record.proto.noteToSelfMarkedUnread, isGroup = false)
|
||||
|
||||
db.updateAll(TABLE_NAME)
|
||||
.values(PINNED_ORDER to null)
|
||||
@@ -1631,6 +1631,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
|
||||
if (pinnedRecipient != null) {
|
||||
getOrCreateThreadIdFor(pinnedRecipient)
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(PINNED_ORDER to pinnedPosition, ACTIVE to 1)
|
||||
.where("$RECIPIENT_ID = ?", pinnedRecipient.id)
|
||||
@@ -1644,11 +1646,11 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
private fun applyStorageSyncUpdate(recipientId: RecipientId, archived: Boolean, forcedUnread: Boolean) {
|
||||
private fun applyStorageSyncUpdate(recipientId: RecipientId, archived: Boolean, forcedUnread: Boolean, isGroup: Boolean) {
|
||||
val values = ContentValues()
|
||||
values.put(ARCHIVED, if (archived) 1 else 0)
|
||||
|
||||
val threadId: Long? = getThreadIdFor(recipientId)
|
||||
val threadId: Long? = if (archived) getOrCreateThreadIdFor(recipientId, isGroup) else getThreadIdFor(recipientId)
|
||||
|
||||
if (forcedUnread) {
|
||||
values.put(READ, ReadStatus.FORCED_UNREAD.serialize())
|
||||
|
||||
@@ -1272,7 +1272,7 @@ final class GroupManagerV2 {
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(groupId);
|
||||
GV2UpdateDescription updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, groupMutation, signedGroupChange);
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis());
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis(), false);
|
||||
|
||||
|
||||
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||
|
||||
@@ -672,7 +672,7 @@ class GroupsV2StateProcessor private constructor(
|
||||
)
|
||||
|
||||
val updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null)
|
||||
val leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis())
|
||||
val leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis(), isSelfGroupAdd = false)
|
||||
|
||||
try {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
@@ -728,10 +728,17 @@ class GroupsV2StateProcessor private constructor(
|
||||
)
|
||||
|
||||
if (outgoing) {
|
||||
val isSelfGroupAdd = updateDescription
|
||||
.groupChangeUpdate!!
|
||||
.updates
|
||||
.asSequence()
|
||||
.mapNotNull { it.groupMemberJoinedUpdate }
|
||||
.any { serviceIds.matches(it.newMemberAci) }
|
||||
|
||||
try {
|
||||
val recipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val outgoingMessage = OutgoingMessage.groupUpdateMessage(recipient, updateDescription, timestamp)
|
||||
val outgoingMessage = OutgoingMessage.groupUpdateMessage(recipient, updateDescription, timestamp, isSelfGroupAdd)
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, null).messageId
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class AccountConsistencyWorkerJob private constructor(parameters: Parameters) :
|
||||
|
||||
@JvmStatic
|
||||
fun enqueueIfNecessary() {
|
||||
if (System.currentTimeMillis() - SignalStore.misc.lastConsistencyCheckTime > 3.days.inWholeMilliseconds) {
|
||||
if (SignalStore.account.isPrimaryDevice && System.currentTimeMillis() - SignalStore.misc.lastConsistencyCheckTime > 3.days.inWholeMilliseconds) {
|
||||
AppDependencies.jobManager.add(AccountConsistencyWorkerJob())
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,11 @@ class AccountConsistencyWorkerJob private constructor(parameters: Parameters) :
|
||||
return
|
||||
}
|
||||
|
||||
if (SignalStore.account.isLinkedDevice) {
|
||||
Log.i(TAG, "Linked device, skipping.")
|
||||
return
|
||||
}
|
||||
|
||||
val aciProfile: SignalServiceProfile = ProfileUtil.retrieveProfileSync(context, Recipient.self(), SignalServiceProfile.RequestType.PROFILE, false).profile
|
||||
val encodedAciPublicKey = Base64.encodeWithPadding(SignalStore.account.aciIdentityKey.publicKey.serialize())
|
||||
|
||||
|
||||
@@ -88,6 +88,11 @@ public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
Log.i(TAG, "Linked device, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] rawProfileKey = Recipient.self().getProfileKey();
|
||||
|
||||
if (rawProfileKey == null) {
|
||||
@@ -160,6 +165,11 @@ public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob {
|
||||
public void onRun()
|
||||
throws IOException, GroupNotAMemberException, GroupChangeFailedException, GroupInsufficientRightsException, GroupChangeBusyException
|
||||
{
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
Log.i(TAG, "Linked device, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Ensuring profile key up to date on group " + groupId);
|
||||
GroupManager.updateSelfProfileKeyInGroup(context, groupId);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -41,6 +42,16 @@ public final class ProfileUploadJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SignalStore.account().isLinkedDevice() && !SignalStore.registration().hasDownloadedProfile()) {
|
||||
Log.w(TAG, "Attempting to upload profile before downloading, forcing download first");
|
||||
AppDependencies.getJobManager()
|
||||
.startChain(new RefreshOwnProfileJob())
|
||||
.then(new ProfileUploadJob())
|
||||
.enqueue();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileUtil.uploadProfile(context);
|
||||
Log.i(TAG, "Profile uploaded.");
|
||||
}
|
||||
|
||||
@@ -159,6 +159,8 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
profileAndCredential.getExpiringProfileKeyCredential()
|
||||
.ifPresent(expiringProfileKeyCredential -> setExpiringProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), expiringProfileKeyCredential));
|
||||
|
||||
SignalStore.registration().setHasDownloadedProfile(true);
|
||||
|
||||
StoryOnboardingDownloadJob.Companion.enqueueIfNeeded();
|
||||
|
||||
checkUsernameIsInSync();
|
||||
|
||||
@@ -6,8 +6,8 @@ import androidx.annotation.Nullable;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
|
||||
/**
|
||||
* A job that effectively debounces thread updates through a combination of having a max instance count
|
||||
@@ -18,34 +18,39 @@ public final class ThreadUpdateJob extends BaseJob {
|
||||
public static final String KEY = "ThreadUpdateJob";
|
||||
|
||||
private static final String KEY_THREAD_ID = "thread_id";
|
||||
private static final String KEY_UNARCHIVE = "unarchive";
|
||||
|
||||
private static final long DEBOUNCE_INTERVAL = 500;
|
||||
private static final long DEBOUNCE_INTERVAL_WITH_BACKLOG = 3000;
|
||||
|
||||
private final long threadId;
|
||||
private final long threadId;
|
||||
private final boolean unarchive;
|
||||
|
||||
private ThreadUpdateJob(long threadId) {
|
||||
private ThreadUpdateJob(long threadId, boolean unarchive) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue("ThreadUpdateJob_" + threadId)
|
||||
.setMaxInstancesForQueue(2)
|
||||
.build(),
|
||||
threadId);
|
||||
threadId,
|
||||
unarchive);
|
||||
}
|
||||
|
||||
private ThreadUpdateJob(@NonNull Parameters parameters, long threadId) {
|
||||
private ThreadUpdateJob(@NonNull Parameters parameters, long threadId, boolean unarchive) {
|
||||
super(parameters);
|
||||
this.threadId = threadId;
|
||||
this.threadId = threadId;
|
||||
this.unarchive = unarchive;
|
||||
}
|
||||
|
||||
public static void enqueue(long threadId) {
|
||||
public static void enqueue(long threadId, boolean unarchive) {
|
||||
SignalDatabase.runPostSuccessfulTransaction(KEY + threadId, () -> {
|
||||
AppDependencies.getJobManager().add(new ThreadUpdateJob(threadId));
|
||||
AppDependencies.getJobManager().add(new ThreadUpdateJob(threadId, unarchive));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return new JsonJobData.Builder().putLong(KEY_THREAD_ID, threadId).serialize();
|
||||
return new JsonJobData.Builder().putLong(KEY_THREAD_ID, threadId)
|
||||
.putBoolean(KEY_UNARCHIVE, unarchive)
|
||||
.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +60,7 @@ public final class ThreadUpdateJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
SignalDatabase.threads().update(threadId, true, true);
|
||||
SignalDatabase.threads().update(threadId, unarchive, true);
|
||||
if (!AppDependencies.getIncomingMessageObserver().getDecryptionDrained()) {
|
||||
ThreadUtil.sleep(DEBOUNCE_INTERVAL_WITH_BACKLOG);
|
||||
}
|
||||
@@ -74,7 +79,7 @@ public final class ThreadUpdateJob extends BaseJob {
|
||||
@Override
|
||||
public @NonNull ThreadUpdateJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
return new ThreadUpdateJob(parameters, data.getLong(KEY_THREAD_ID));
|
||||
return new ThreadUpdateJob(parameters, data.getLong(KEY_THREAD_ID), data.getBooleanOrDefault(KEY_UNARCHIVE, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
|
||||
private const val REGISTRATION_COMPLETE = "registration.complete"
|
||||
private const val PIN_REQUIRED = "registration.pin_required"
|
||||
private const val HAS_UPLOADED_PROFILE = "registration.has_uploaded_profile"
|
||||
private const val HAS_DOWNLOADED_PROFILE = "registration.has_downloaded_profile"
|
||||
private const val SESSION_E164 = "registration.session_e164"
|
||||
private const val SESSION_ID = "registration.session_id"
|
||||
private const val LOCAL_REGISTRATION_DATA = "registration.local_registration_data"
|
||||
@@ -32,6 +33,7 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
|
||||
store
|
||||
.beginWrite()
|
||||
.putBoolean(HAS_UPLOADED_PROFILE, false)
|
||||
.putBoolean(HAS_DOWNLOADED_PROFILE, false)
|
||||
.putBoolean(REGISTRATION_COMPLETE, false)
|
||||
.putBoolean(PIN_REQUIRED, true)
|
||||
.putBlob(RESTORE_DECISION_STATE, RestoreDecisionState.Start.encode())
|
||||
@@ -67,6 +69,10 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
|
||||
|
||||
@get:JvmName("hasUploadedProfile")
|
||||
var hasUploadedProfile: Boolean by booleanValue(HAS_UPLOADED_PROFILE, true)
|
||||
|
||||
@get:JvmName("hasDownloadedProfile")
|
||||
var hasDownloadedProfile: Boolean by booleanValue(HAS_DOWNLOADED_PROFILE, true)
|
||||
|
||||
var sessionId: String? by stringValue(SESSION_ID, null)
|
||||
var sessionE164: String? by stringValue(SESSION_E164, null)
|
||||
|
||||
|
||||
@@ -135,6 +135,10 @@ public final class Megaphones {
|
||||
}
|
||||
|
||||
private static boolean shouldShowLinkedDeviceInactiveMegaphone() {
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LeastActiveLinkedDevice device = SignalStore.misc().getLeastActiveLinkedDevice();
|
||||
if (device == null) {
|
||||
return false;
|
||||
@@ -487,11 +491,11 @@ public final class Megaphones {
|
||||
}
|
||||
|
||||
private static boolean shouldShowOnboardingMegaphone(@NonNull Context context) {
|
||||
return SignalStore.onboarding().hasOnboarding(context);
|
||||
return SignalStore.account().isPrimaryDevice() && SignalStore.onboarding().hasOnboarding(context);
|
||||
}
|
||||
|
||||
private static boolean shouldShowNewLinkedDeviceMegaphone() {
|
||||
return SignalStore.misc().getNewLinkedDeviceId() > 0 && !NotificationChannels.getInstance().areNotificationsEnabled();
|
||||
return SignalStore.account().isPrimaryDevice() && SignalStore.misc().getNewLinkedDeviceId() > 0 && !NotificationChannels.getInstance().areNotificationsEnabled();
|
||||
}
|
||||
|
||||
private static boolean shouldShowTurnOffCircumventionMegaphone() {
|
||||
@@ -542,7 +546,8 @@ public final class Megaphones {
|
||||
long phoneNumberDiscoveryDisabledAt = SignalStore.phoneNumberPrivacy().getPhoneNumberDiscoverabilityModeTimestamp();
|
||||
PhoneNumberDiscoverabilityMode listingMode = SignalStore.phoneNumberPrivacy().getPhoneNumberDiscoverabilityMode();
|
||||
|
||||
return !hasUsername &&
|
||||
return SignalStore.account().isPrimaryDevice() &&
|
||||
!hasUsername &&
|
||||
listingMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE &&
|
||||
!hasCompleted &&
|
||||
phoneNumberDiscoveryDisabledAt > 0 &&
|
||||
@@ -550,7 +555,7 @@ public final class Megaphones {
|
||||
}
|
||||
|
||||
private static boolean shouldShowPnpLaunchMegaphone() {
|
||||
return TextUtils.isEmpty(SignalStore.account().getUsername()) && !SignalStore.uiHints().hasCompletedUsernameOnboarding();
|
||||
return SignalStore.account().isPrimaryDevice() && TextUtils.isEmpty(SignalStore.account().getUsername()) && !SignalStore.uiHints().hasCompletedUsernameOnboarding();
|
||||
}
|
||||
|
||||
private static boolean shouldShowTurnOnBackupsMegaphone(@NonNull Context context) {
|
||||
@@ -562,7 +567,7 @@ public final class Megaphones {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SignalStore.account().isRegistered() || TextSecurePreferences.isUnauthorizedReceived(context)) {
|
||||
if (!SignalStore.account().isRegistered() || TextSecurePreferences.isUnauthorizedReceived(context) || SignalStore.account().isLinkedDevice()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -580,7 +585,7 @@ public final class Megaphones {
|
||||
}
|
||||
|
||||
private static boolean shouldShowBackupSchedulePermissionMegaphone(@NonNull Context context) {
|
||||
return Build.VERSION.SDK_INT >= 31 && SignalStore.settings().isBackupEnabled() && !ServiceUtil.getAlarmManager(context).canScheduleExactAlarms();
|
||||
return SignalStore.account().isPrimaryDevice() && Build.VERSION.SDK_INT >= 31 && SignalStore.settings().isBackupEnabled() && !ServiceUtil.getAlarmManager(context).canScheduleExactAlarms();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,10 @@ class PinsForAllSchedule implements MegaphoneSchedule {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pinCreationFailedDuringRegistration()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SignalStore.pin().arePinRemindersEnabled()) {
|
||||
return false;
|
||||
}
|
||||
@@ -22,6 +26,10 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long lastReminderTime = SignalStore.pin().getLastReminderTime();
|
||||
long interval = SignalStore.pin().getCurrentInterval();
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ class VerifyBackupKeyReminderSchedule : MegaphoneSchedule {
|
||||
return false
|
||||
}
|
||||
|
||||
if (SignalStore.account.isLinkedDevice) {
|
||||
return false
|
||||
}
|
||||
|
||||
val lastVerifiedTime = SignalStore.backup.lastVerifyKeyTime
|
||||
val previouslySnoozed = SignalStore.backup.hasSnoozedVerified
|
||||
val isFirstReminder = !SignalStore.backup.hasVerifiedBefore
|
||||
|
||||
@@ -60,7 +60,8 @@ data class OutgoingMessage(
|
||||
val isBlocked: Boolean = false,
|
||||
val isUnblocked: Boolean = false,
|
||||
val poll: Poll? = null,
|
||||
val messageExtras: MessageExtras? = null
|
||||
val messageExtras: MessageExtras? = null,
|
||||
val isSelfGroupAdd: Boolean = false
|
||||
) {
|
||||
|
||||
val isV2Group: Boolean = messageGroupContext != null && GroupV2UpdateMessageUtil.isGroupV2(messageGroupContext)
|
||||
@@ -240,7 +241,7 @@ data class OutgoingMessage(
|
||||
* Helper for creating a group update message when a state change occurs and needs to be sent to others.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun groupUpdateMessage(threadRecipient: Recipient, update: GV2UpdateDescription, sentTimeMillis: Long): OutgoingMessage {
|
||||
fun groupUpdateMessage(threadRecipient: Recipient, update: GV2UpdateDescription, sentTimeMillis: Long, isSelfGroupAdd: Boolean): OutgoingMessage {
|
||||
val messageExtras = MessageExtras(gv2UpdateDescription = update)
|
||||
val groupContext = MessageGroupContext(update.gv2ChangeDescription!!)
|
||||
|
||||
@@ -251,7 +252,8 @@ data class OutgoingMessage(
|
||||
isGroup = true,
|
||||
isGroupUpdate = true,
|
||||
isSecure = true,
|
||||
messageExtras = messageExtras
|
||||
messageExtras = messageExtras,
|
||||
isSelfGroupAdd = isSelfGroupAdd
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,11 @@ object RegistrationRepository {
|
||||
suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) =
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.v(TAG, "registerAccountLocally()")
|
||||
if (data.linkedDeviceInfo != null) {
|
||||
SignalStore.account.deviceId = data.linkedDeviceInfo.deviceId
|
||||
SignalStore.account.deviceName = data.linkedDeviceInfo.deviceName
|
||||
}
|
||||
|
||||
val aciIdentityKeyPair = data.getAciIdentityKeyPair()
|
||||
val pniIdentityKeyPair = data.getPniIdentityKeyPair()
|
||||
SignalStore.account.restoreAciIdentityKeyFromBackup(aciIdentityKeyPair.publicKey.serialize(), aciIdentityKeyPair.privateKey.serialize())
|
||||
@@ -219,9 +224,6 @@ object RegistrationRepository {
|
||||
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now)
|
||||
|
||||
if (data.linkedDeviceInfo != null) {
|
||||
SignalStore.account.deviceId = data.linkedDeviceInfo.deviceId
|
||||
SignalStore.account.deviceName = data.linkedDeviceInfo.deviceName
|
||||
|
||||
if (data.linkedDeviceInfo.accountEntropyPool != null) {
|
||||
SignalStore.account.setAccountEntropyPoolFromPrimaryDevice(AccountEntropyPool(data.linkedDeviceInfo.accountEntropyPool))
|
||||
}
|
||||
@@ -254,7 +256,6 @@ object RegistrationRepository {
|
||||
RotateSignedPreKeyListener.schedule(context)
|
||||
} else {
|
||||
SignalStore.account.isMultiDevice = true
|
||||
SignalStore.registration.hasUploadedProfile = true
|
||||
jobManager.runJobBlocking(RefreshOwnProfileJob(), 30.seconds)
|
||||
|
||||
jobManager.add(RotateCertificateJob())
|
||||
|
||||
@@ -51,7 +51,7 @@ object StorageServiceRestore {
|
||||
val isMissingProfileData = RegistrationRepository.isMissingProfileData()
|
||||
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
if (!isMissingProfileData) {
|
||||
if (!isMissingProfileData && SignalStore.account.isPrimaryDevice) {
|
||||
AppDependencies.jobManager.add(ProfileUploadJob())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class RegistrationUtil {
|
||||
if (!SignalStore.registration().isRegistrationComplete() &&
|
||||
SignalStore.account().isRegistered() &&
|
||||
!Recipient.self().getProfileName().isEmpty() &&
|
||||
(SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut()) &&
|
||||
(SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut() || SignalStore.account().isLinkedDevice()) &&
|
||||
RestoreDecisionStateUtil.isTerminal(SignalStore.registration().getRestoreDecisionState()))
|
||||
{
|
||||
Log.i(TAG, "Marking registration completed.", new Throwable());
|
||||
|
||||
@@ -74,6 +74,11 @@ public final class ProfileUtil {
|
||||
*/
|
||||
@WorkerThread
|
||||
public static void handleSelfProfileKeyChange() {
|
||||
if (SignalStore.account().isLinkedDevice()) {
|
||||
Log.i(TAG, "Linked devices shouldn't rotate self profile key after initial link");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Job> gv2UpdateJobs = SignalDatabase.groups()
|
||||
.getAllGroupV2Ids()
|
||||
.stream()
|
||||
|
||||
@@ -101,6 +101,7 @@ class RegistrationUtilTest {
|
||||
every { Recipient.self() } returns Recipient(profileName = ProfileName.fromParts("Dark", "Helmet"))
|
||||
every { signalStore.svr.hasPin() } returns false
|
||||
every { signalStore.svr.hasOptedOut() } returns false
|
||||
every { signalStore.account.isLinkedDevice } returns false
|
||||
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user