diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 105ab74e2d..bc3bbb211e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.FcmRefreshJob; import org.thoughtcrime.securesms.jobs.FontDownloaderJob; +import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; @@ -201,6 +202,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr .addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount()) .addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob())) .addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary) + .addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary) .execute(); Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java index e73a2e356d..0df36fdaeb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java @@ -1,8 +1,16 @@ package org.thoughtcrime.securesms.jobs; +import androidx.annotation.AnyThread; import androidx.annotation.NonNull; +import com.google.protobuf.ByteString; + +import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.signal.storageservice.protos.groups.local.DecryptedMember; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeFailedException; import org.thoughtcrime.securesms.groups.GroupId; @@ -13,10 +21,17 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -66,6 +81,67 @@ public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob { groupId); } + /** + * Updates GV2 groups with the correct profile key if we find any that are out of date. Will run at most once per day. + */ + @AnyThread + public static void enqueueForGroupsIfNecessary() { + if (!SignalStore.account().isRegistered() || SignalStore.account().getAci() == null || !Recipient.self().isRegistered()) { + Log.w(TAG, "Not yet registered!"); + return; + } + + byte[] rawProfileKey = Recipient.self().getProfileKey(); + + if (rawProfileKey == null) { + Log.w(TAG, "No profile key set!"); + return; + } + + ByteString selfProfileKey = ByteString.copyFrom(rawProfileKey); + + long timeSinceLastCheck = System.currentTimeMillis() - SignalStore.misc().getLastGv2ProfileCheckTime(); + + if (timeSinceLastCheck < TimeUnit.DAYS.toMillis(1)) { + Log.d(TAG, "Too soon. Last check was " + timeSinceLastCheck + " ms ago."); + return; + } + + Log.i(TAG, "Running routine check."); + + SignalStore.misc().setLastGv2ProfileCheckTime(System.currentTimeMillis()); + + SignalExecutors.BOUNDED.execute(() -> { + boolean foundMismatch = false; + + for (GroupId.V2 id : SignalDatabase.groups().getAllGroupV2Ids()) { + Optional group = SignalDatabase.groups().getGroup(id); + if (!group.isPresent()) { + Log.w(TAG, "Group " + group + " no longer exists?"); + continue; + } + + ByteString selfUuidBytes = UuidUtil.toByteString(Recipient.self().requireServiceId().uuid()); + DecryptedMember selfMember = group.get().requireV2GroupProperties().getDecryptedGroup().getMembersList() + .stream() + .filter(m -> m.getUuid().equals(selfUuidBytes)) + .findFirst() + .orElse(null); + + if (selfMember != null && !selfMember.getProfileKey().equals(selfProfileKey)) { + Log.w(TAG, "Profile key mismatch for group " + id + " -- enqueueing job"); + foundMismatch = true; + ApplicationDependencies.getJobManager().add(GroupV2UpdateSelfProfileKeyJob.withQueueLimits(id)); + } + } + + if (!foundMismatch) { + Log.i(TAG, "No mismatches found."); + } + }); + } + + private GroupV2UpdateSelfProfileKeyJob(@NonNull Parameters parameters, @NonNull GroupId.V2 groupId) { super(parameters); this.groupId = groupId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java index f3ad137c73..39d6c5364e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java @@ -18,6 +18,7 @@ public final class MiscellaneousValues extends SignalStoreValues { private static final String CHANGE_NUMBER_LOCK = "misc.change_number.lock"; private static final String CENSORSHIP_LAST_CHECK_TIME = "misc.censorship.last_check_time"; private static final String CENSORSHIP_SERVICE_REACHABLE = "misc.censorship.service_reachable"; + private static final String LAST_GV2_PROFILE_CHECK_TIME = "misc.last_gv2_profile_check_time"; MiscellaneousValues(@NonNull KeyValueStore store) { super(store); @@ -128,4 +129,12 @@ public final class MiscellaneousValues extends SignalStoreValues { public void setServiceReachableWithoutCircumvention(boolean value) { putBoolean(CENSORSHIP_SERVICE_REACHABLE, value); } + + public long getLastGv2ProfileCheckTime() { + return getLong(LAST_GV2_PROFILE_CHECK_TIME, 0); + } + + public void setLastGv2ProfileCheckTime(long value) { + putLong(LAST_GV2_PROFILE_CHECK_TIME, value); + } }