Search in sources :

Example 1 with Stopwatch

use of org.thoughtcrime.securesms.util.Stopwatch in project Signal-Android by WhisperSystems.

the class RegistrationLockFragment method handleSuccessfulPinEntry.

@Override
protected void handleSuccessfulPinEntry(@NonNull String pin) {
    SignalStore.pinValues().setKeyboardType(getPinEntryKeyboardType());
    SimpleTask.run(() -> {
        SignalStore.onboarding().clearAll();
        Stopwatch stopwatch = new Stopwatch("RegistrationLockRestore");
        ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
        stopwatch.split("AccountRestore");
        ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10));
        stopwatch.split("ContactRestore");
        try {
            FeatureFlags.refreshSync();
        } catch (IOException e) {
            Log.w(TAG, "Failed to refresh flags.", e);
        }
        stopwatch.split("FeatureFlags");
        stopwatch.stop(TAG);
        return null;
    }, none -> {
        cancelSpinning(pinButton);
        SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), RegistrationLockFragmentDirections.actionSuccessfulRegistration());
    });
}
Also used : StorageSyncJob(org.thoughtcrime.securesms.jobs.StorageSyncJob) StorageAccountRestoreJob(org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) IOException(java.io.IOException)

Example 2 with Stopwatch

use of org.thoughtcrime.securesms.util.Stopwatch in project Signal-Android by WhisperSystems.

the class StorageSyncJob method performSync.

private boolean performSync() throws IOException, RetryLaterException, InvalidKeyException {
    final Stopwatch stopwatch = new Stopwatch("StorageSync");
    final SQLiteDatabase db = SignalDatabase.getRawDatabase();
    final SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
    final UnknownStorageIdDatabase storageIdDatabase = SignalDatabase.unknownStorageIds();
    final StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
    final SignalStorageManifest localManifest = SignalStore.storageService().getManifest();
    final SignalStorageManifest remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.getVersion()).or(localManifest);
    stopwatch.split("remote-manifest");
    Recipient self = freshSelf();
    boolean needsMultiDeviceSync = false;
    boolean needsForcePush = false;
    if (self.getStorageServiceId() == null) {
        Log.w(TAG, "No storageId for self. Generating.");
        SignalDatabase.recipients().updateStorageId(self.getId(), StorageSyncHelper.generateKey());
        self = freshSelf();
    }
    Log.i(TAG, "Our version: " + localManifest.getVersion() + ", their version: " + remoteManifest.getVersion());
    if (remoteManifest.getVersion() > localManifest.getVersion()) {
        Log.i(TAG, "[Remote Sync] Newer manifest version found!");
        List<StorageId> localStorageIdsBeforeMerge = getAllLocalStorageIds(context, self);
        IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIdsBeforeMerge);
        if (idDifference.hasTypeMismatches() && SignalStore.account().isPrimaryDevice()) {
            Log.w(TAG, "[Remote Sync] Found type mismatches in the ID sets! Scheduling a force push after this sync completes.");
            needsForcePush = true;
        }
        Log.i(TAG, "[Remote Sync] Pre-Merge ID Difference :: " + idDifference);
        stopwatch.split("remote-id-diff");
        if (!idDifference.isEmpty()) {
            Log.i(TAG, "[Remote Sync] Retrieving records for key difference.");
            List<SignalStorageRecord> remoteOnly = accountManager.readStorageRecords(storageServiceKey, idDifference.getRemoteOnlyIds());
            stopwatch.split("remote-records");
            if (remoteOnly.size() != idDifference.getRemoteOnlyIds().size()) {
                Log.w(TAG, "[Remote Sync] Could not find all remote-only records! Requested: " + idDifference.getRemoteOnlyIds().size() + ", Found: " + remoteOnly.size() + ". These stragglers should naturally get deleted during the sync.");
            }
            List<SignalContactRecord> remoteContacts = new LinkedList<>();
            List<SignalGroupV1Record> remoteGv1 = new LinkedList<>();
            List<SignalGroupV2Record> remoteGv2 = new LinkedList<>();
            List<SignalAccountRecord> remoteAccount = new LinkedList<>();
            List<SignalStorageRecord> remoteUnknown = new LinkedList<>();
            for (SignalStorageRecord remote : remoteOnly) {
                if (remote.getContact().isPresent()) {
                    remoteContacts.add(remote.getContact().get());
                } else if (remote.getGroupV1().isPresent()) {
                    remoteGv1.add(remote.getGroupV1().get());
                } else if (remote.getGroupV2().isPresent()) {
                    remoteGv2.add(remote.getGroupV2().get());
                } else if (remote.getAccount().isPresent()) {
                    remoteAccount.add(remote.getAccount().get());
                } else if (remote.getId().isUnknown()) {
                    remoteUnknown.add(remote);
                } else {
                    Log.w(TAG, "Bad record! Type is a known value (" + remote.getId().getType() + "), but doesn't have a matching inner record. Dropping it.");
                }
            }
            db.beginTransaction();
            try {
                self = freshSelf();
                Log.i(TAG, "[Remote Sync] Remote-Only :: Contacts: " + remoteContacts.size() + ", GV1: " + remoteGv1.size() + ", GV2: " + remoteGv2.size() + ", Account: " + remoteAccount.size());
                new ContactRecordProcessor(context, self).process(remoteContacts, StorageSyncHelper.KEY_GENERATOR);
                new GroupV1RecordProcessor(context).process(remoteGv1, StorageSyncHelper.KEY_GENERATOR);
                new GroupV2RecordProcessor(context).process(remoteGv2, StorageSyncHelper.KEY_GENERATOR);
                self = freshSelf();
                new AccountRecordProcessor(context, self).process(remoteAccount, StorageSyncHelper.KEY_GENERATOR);
                List<SignalStorageRecord> unknownInserts = remoteUnknown;
                List<StorageId> unknownDeletes = Stream.of(idDifference.getLocalOnlyIds()).filter(StorageId::isUnknown).toList();
                Log.i(TAG, "[Remote Sync] Unknowns :: " + unknownInserts.size() + " inserts, " + unknownDeletes.size() + " deletes");
                storageIdDatabase.insert(unknownInserts);
                storageIdDatabase.delete(unknownDeletes);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
                ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
                stopwatch.split("remote-merge-transaction");
            }
        } else {
            Log.i(TAG, "[Remote Sync] Remote version was newer, but there were no remote-only IDs.");
        }
    } else if (remoteManifest.getVersion() < localManifest.getVersion()) {
        Log.w(TAG, "[Remote Sync] Remote version was older. User might have switched accounts.");
    }
    if (remoteManifest != localManifest) {
        Log.i(TAG, "[Remote Sync] Saved new manifest. Now at version: " + remoteManifest.getVersion());
        SignalStore.storageService().setManifest(remoteManifest);
    }
    Log.i(TAG, "We are up-to-date with the remote storage state.");
    final WriteOperationResult remoteWriteOperation;
    db.beginTransaction();
    try {
        self = freshSelf();
        List<StorageId> localStorageIds = getAllLocalStorageIds(context, self);
        IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIds);
        List<SignalStorageRecord> remoteInserts = buildLocalStorageRecords(context, self, idDifference.getLocalOnlyIds());
        List<byte[]> remoteDeletes = Stream.of(idDifference.getRemoteOnlyIds()).map(StorageId::getRaw).toList();
        Log.i(TAG, "ID Difference :: " + idDifference);
        remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.getVersion() + 1, localStorageIds), remoteInserts, remoteDeletes);
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
        stopwatch.split("local-data-transaction");
    }
    if (!remoteWriteOperation.isEmpty()) {
        Log.i(TAG, "We have something to write remotely.");
        Log.i(TAG, "WriteOperationResult :: " + remoteWriteOperation);
        StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self);
        Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, remoteWriteOperation.getManifest(), remoteWriteOperation.getInserts(), remoteWriteOperation.getDeletes());
        if (conflict.isPresent()) {
            Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.");
            throw new RetryLaterException();
        }
        Log.i(TAG, "Saved new manifest. Now at version: " + remoteWriteOperation.getManifest().getVersion());
        SignalStore.storageService().setManifest(remoteWriteOperation.getManifest());
        stopwatch.split("remote-write");
        needsMultiDeviceSync = true;
    } else {
        Log.i(TAG, "No remote writes needed. Still at version: " + remoteManifest.getVersion());
    }
    if (needsForcePush && SignalStore.account().isPrimaryDevice()) {
        Log.w(TAG, "Scheduling a force push.");
        ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
    }
    stopwatch.stop(TAG);
    return needsMultiDeviceSync;
}
Also used : SignalContactRecord(org.whispersystems.signalservice.api.storage.SignalContactRecord) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) StorageId(org.whispersystems.signalservice.api.storage.StorageId) GroupV2RecordProcessor(org.thoughtcrime.securesms.storage.GroupV2RecordProcessor) SignalGroupV2Record(org.whispersystems.signalservice.api.storage.SignalGroupV2Record) UnknownStorageIdDatabase(org.thoughtcrime.securesms.database.UnknownStorageIdDatabase) StorageKey(org.whispersystems.signalservice.api.storage.StorageKey) SignalAccountRecord(org.whispersystems.signalservice.api.storage.SignalAccountRecord) SignalStorageManifest(org.whispersystems.signalservice.api.storage.SignalStorageManifest) IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) GroupV1RecordProcessor(org.thoughtcrime.securesms.storage.GroupV1RecordProcessor) SignalServiceAccountManager(org.whispersystems.signalservice.api.SignalServiceAccountManager) SignalStorageRecord(org.whispersystems.signalservice.api.storage.SignalStorageRecord) Recipient(org.thoughtcrime.securesms.recipients.Recipient) WriteOperationResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult) LinkedList(java.util.LinkedList) SignalGroupV1Record(org.whispersystems.signalservice.api.storage.SignalGroupV1Record) AccountRecordProcessor(org.thoughtcrime.securesms.storage.AccountRecordProcessor) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) ContactRecordProcessor(org.thoughtcrime.securesms.storage.ContactRecordProcessor) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException)

Example 3 with Stopwatch

use of org.thoughtcrime.securesms.util.Stopwatch in project Signal-Android by WhisperSystems.

the class DirectoryHelper method refreshNumbers.

@WorkerThread
private static void refreshNumbers(@NonNull Context context, @NonNull Set<String> databaseNumbers, @NonNull Set<String> systemNumbers, boolean notifyOfNewUsers, boolean removeSystemContactEntryForMissing) throws IOException {
    RecipientDatabase recipientDatabase = SignalDatabase.recipients();
    Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
    if (allNumbers.isEmpty()) {
        Log.w(TAG, "No numbers to refresh!");
        return;
    }
    Stopwatch stopwatch = new Stopwatch("refresh");
    DirectoryResult result;
    if (FeatureFlags.cdsh()) {
        result = ContactDiscoveryV3.getDirectoryResult(databaseNumbers, systemNumbers);
    } else {
        result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
    }
    stopwatch.split("network");
    if (result.getNumberRewrites().size() > 0) {
        Log.i(TAG, "[getDirectoryResult] Need to rewrite some numbers.");
        recipientDatabase.updatePhoneNumbers(result.getNumberRewrites());
    }
    Map<RecipientId, ACI> aciMap = recipientDatabase.bulkProcessCdsResult(result.getRegisteredNumbers());
    Set<String> activeNumbers = result.getRegisteredNumbers().keySet();
    Set<RecipientId> activeIds = aciMap.keySet();
    Set<RecipientId> inactiveIds = Stream.of(allNumbers).filterNot(activeNumbers::contains).filterNot(n -> result.getNumberRewrites().containsKey(n)).filterNot(n -> result.getIgnoredNumbers().contains(n)).map(recipientDatabase::getOrInsertFromE164).collect(Collectors.toSet());
    stopwatch.split("process-cds");
    UnlistedResult unlistedResult = filterForUnlistedUsers(context, inactiveIds);
    inactiveIds.removeAll(unlistedResult.getPossiblyActive());
    if (unlistedResult.getRetries().size() > 0) {
        Log.i(TAG, "Some profile fetches failed to resolve. Assuming not-inactive for now and scheduling a retry.");
        RetrieveProfileJob.enqueue(unlistedResult.getRetries());
    }
    stopwatch.split("handle-unlisted");
    Set<RecipientId> preExistingRegisteredUsers = new HashSet<>(recipientDatabase.getRegistered());
    recipientDatabase.bulkUpdatedRegisteredStatus(aciMap, inactiveIds);
    stopwatch.split("update-registered");
    updateContactsDatabase(context, activeIds, removeSystemContactEntryForMissing, result.getNumberRewrites());
    stopwatch.split("contacts-db");
    if (TextSecurePreferences.isMultiDevice(context)) {
        ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob());
    }
    if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context) && notifyOfNewUsers) {
        Set<RecipientId> systemContacts = new HashSet<>(recipientDatabase.getSystemContacts());
        Set<RecipientId> newlyRegisteredSystemContacts = new HashSet<>(activeIds);
        newlyRegisteredSystemContacts.removeAll(preExistingRegisteredUsers);
        newlyRegisteredSystemContacts.retainAll(systemContacts);
        notifyNewUsers(context, newlyRegisteredSystemContacts);
    } else {
        TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
    }
    stopwatch.stop(TAG);
}
Also used : SignalStore(org.thoughtcrime.securesms.keyvalue.SignalStore) NonNull(androidx.annotation.NonNull) R(org.thoughtcrime.securesms.R) SignalServiceAddress(org.whispersystems.signalservice.api.push.SignalServiceAddress) Manifest(android.Manifest) ProfileAndCredential(org.whispersystems.signalservice.api.profiles.ProfileAndCredential) ContactsContract(android.provider.ContactsContract) BulkOperationsHandle(org.thoughtcrime.securesms.database.RecipientDatabase.BulkOperationsHandle) RegisteredState(org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) StorageSyncHelper(org.thoughtcrime.securesms.storage.StorageSyncHelper) ContentResolver(android.content.ContentResolver) Map(java.util.Map) SignalProtocolAddress(org.whispersystems.libsignal.SignalProtocolAddress) Recipient(org.thoughtcrime.securesms.recipients.Recipient) AccountManager(android.accounts.AccountManager) ACI(org.whispersystems.signalservice.api.push.ACI) Account(android.accounts.Account) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) Collection(java.util.Collection) Set(java.util.Set) SetUtil(org.thoughtcrime.securesms.util.SetUtil) Objects(java.util.Objects) Log(org.signal.core.util.logging.Log) FeatureFlags(org.thoughtcrime.securesms.util.FeatureFlags) List(java.util.List) Nullable(androidx.annotation.Nullable) StorageSyncJob(org.thoughtcrime.securesms.jobs.StorageSyncJob) ProfileService(org.whispersystems.signalservice.api.services.ProfileService) BuildConfig(org.thoughtcrime.securesms.BuildConfig) InsertResult(org.thoughtcrime.securesms.database.MessageDatabase.InsertResult) Context(android.content.Context) SignalDatabase(org.thoughtcrime.securesms.database.SignalDatabase) Stream(com.annimon.stream.Stream) Util(org.thoughtcrime.securesms.util.Util) WorkerThread(androidx.annotation.WorkerThread) RemoteException(android.os.RemoteException) RetrieveProfileJob(org.thoughtcrime.securesms.jobs.RetrieveProfileJob) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) SignalServiceProfile(org.whispersystems.signalservice.api.profiles.SignalServiceProfile) TextSecurePreferences(org.thoughtcrime.securesms.util.TextSecurePreferences) HashSet(java.util.HashSet) Schedulers(io.reactivex.rxjava3.schedulers.Schedulers) Pair(org.whispersystems.libsignal.util.Pair) NotificationChannels(org.thoughtcrime.securesms.notifications.NotificationChannels) ProfileUtil(org.thoughtcrime.securesms.util.ProfileUtil) Calendar(java.util.Calendar) Observable(io.reactivex.rxjava3.core.Observable) RegistrationUtil(org.thoughtcrime.securesms.registration.RegistrationUtil) ContactsDatabase(org.thoughtcrime.securesms.contacts.ContactsDatabase) MultiDeviceContactUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob) Cursor(android.database.Cursor) Collectors(com.annimon.stream.Collectors) Permissions(org.thoughtcrime.securesms.permissions.Permissions) UuidUtil(org.whispersystems.signalservice.api.util.UuidUtil) TextUtils(android.text.TextUtils) IOException(java.io.IOException) ServiceResponse(org.whispersystems.signalservice.internal.ServiceResponse) OperationApplicationException(android.content.OperationApplicationException) Optional(org.whispersystems.libsignal.util.guava.Optional) TimeUnit(java.util.concurrent.TimeUnit) CursorUtil(org.thoughtcrime.securesms.util.CursorUtil) IncomingJoinedMessage(org.thoughtcrime.securesms.sms.IncomingJoinedMessage) ContactAccessor(org.thoughtcrime.securesms.contacts.ContactAccessor) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) PhoneNumberFormatter(org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter) Collections(java.util.Collections) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) ACI(org.whispersystems.signalservice.api.push.ACI) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) MultiDeviceContactUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob) HashSet(java.util.HashSet) WorkerThread(androidx.annotation.WorkerThread)

Example 4 with Stopwatch

use of org.thoughtcrime.securesms.util.Stopwatch in project Signal-Android by WhisperSystems.

the class DirectoryHelper method refreshDirectoryFor.

@WorkerThread
public static RegisteredState refreshDirectoryFor(@NonNull Context context, @NonNull Recipient recipient, boolean notifyOfNewUsers) throws IOException {
    Stopwatch stopwatch = new Stopwatch("single");
    RecipientDatabase recipientDatabase = SignalDatabase.recipients();
    RegisteredState originalRegisteredState = recipient.resolve().getRegistered();
    RegisteredState newRegisteredState;
    if (recipient.hasServiceId() && !recipient.hasE164()) {
        boolean isRegistered = ApplicationDependencies.getSignalServiceAccountManager().isIdentifierRegistered(recipient.requireServiceId());
        stopwatch.split("aci-network");
        if (isRegistered) {
            boolean idChanged = recipientDatabase.markRegistered(recipient.getId(), recipient.requireServiceId());
            if (idChanged) {
                Log.w(TAG, "ID changed during refresh by UUID.");
            }
        } else {
            recipientDatabase.markUnregistered(recipient.getId());
        }
        stopwatch.split("aci-disk");
        stopwatch.stop(TAG);
        return isRegistered ? RegisteredState.REGISTERED : RegisteredState.NOT_REGISTERED;
    }
    if (!recipient.getE164().isPresent()) {
        Log.w(TAG, "No ACI or E164?");
        return RegisteredState.NOT_REGISTERED;
    }
    DirectoryResult result = ContactDiscoveryV2.getDirectoryResult(context, recipient.getE164().get());
    stopwatch.split("e164-network");
    if (result.getNumberRewrites().size() > 0) {
        Log.i(TAG, "[getDirectoryResult] Need to rewrite some numbers.");
        recipientDatabase.updatePhoneNumbers(result.getNumberRewrites());
    }
    if (result.getRegisteredNumbers().size() > 0) {
        ACI aci = result.getRegisteredNumbers().values().iterator().next();
        if (aci != null) {
            boolean idChanged = recipientDatabase.markRegistered(recipient.getId(), aci);
            if (idChanged) {
                recipient = Recipient.resolved(recipientDatabase.getByServiceId(aci).get());
            }
        } else {
            Log.w(TAG, "Registered number set had a null ACI!");
        }
    } else if (recipient.hasServiceId() && recipient.isRegistered() && hasCommunicatedWith(recipient)) {
        if (ApplicationDependencies.getSignalServiceAccountManager().isIdentifierRegistered(recipient.requireServiceId())) {
            recipientDatabase.markRegistered(recipient.getId(), recipient.requireServiceId());
        } else {
            recipientDatabase.markUnregistered(recipient.getId());
        }
        stopwatch.split("e164-unlisted-network");
    } else {
        recipientDatabase.markUnregistered(recipient.getId());
    }
    if (Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) {
        updateContactsDatabase(context, Collections.singletonList(recipient.getId()), false, result.getNumberRewrites());
    }
    newRegisteredState = result.getRegisteredNumbers().size() > 0 ? RegisteredState.REGISTERED : RegisteredState.NOT_REGISTERED;
    if (newRegisteredState != originalRegisteredState) {
        ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob());
        ApplicationDependencies.getJobManager().add(new StorageSyncJob());
        if (notifyOfNewUsers && newRegisteredState == RegisteredState.REGISTERED && recipient.resolve().isSystemContact()) {
            notifyNewUsers(context, Collections.singletonList(recipient.getId()));
        }
        StorageSyncHelper.scheduleSyncForDataChange();
    }
    stopwatch.split("e164-disk");
    stopwatch.stop(TAG);
    return newRegisteredState;
}
Also used : RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) MultiDeviceContactUpdateJob(org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob) StorageSyncJob(org.thoughtcrime.securesms.jobs.StorageSyncJob) RegisteredState(org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState) ACI(org.whispersystems.signalservice.api.push.ACI) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) WorkerThread(androidx.annotation.WorkerThread)

Example 5 with Stopwatch

use of org.thoughtcrime.securesms.util.Stopwatch in project Signal-Android by WhisperSystems.

the class RetrieveProfileJob method onRun.

@Override
public void onRun() throws IOException, RetryLaterException {
    if (!SignalStore.account().isRegistered()) {
        Log.w(TAG, "Unregistered. Skipping.");
        return;
    }
    Stopwatch stopwatch = new Stopwatch("RetrieveProfile");
    RecipientDatabase recipientDatabase = SignalDatabase.recipients();
    RecipientUtil.ensureUuidsAreAvailable(context, Stream.of(Recipient.resolvedList(recipientIds)).filter(r -> r.getRegistered() != RecipientDatabase.RegisteredState.NOT_REGISTERED).toList());
    List<Recipient> recipients = Recipient.resolvedList(recipientIds);
    stopwatch.split("resolve-ensure");
    ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(), ApplicationDependencies.getSignalServiceMessageReceiver(), ApplicationDependencies.getSignalWebSocket());
    List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(recipients).filter(Recipient::hasServiceId).map(r -> ProfileUtil.retrieveProfile(context, r, getRequestType(r), profileService).toObservable()).toList();
    stopwatch.split("requests");
    OperationState operationState = Observable.mergeDelayError(requests).observeOn(Schedulers.io(), true).scan(new OperationState(), (state, pair) -> {
        Recipient recipient = pair.first();
        ProfileService.ProfileResponseProcessor processor = new ProfileService.ProfileResponseProcessor(pair.second());
        if (processor.hasResult()) {
            state.profiles.add(processor.getResult(recipient));
        } else if (processor.notFound()) {
            Log.w(TAG, "Failed to find a profile for " + recipient.getId());
            if (recipient.isRegistered()) {
                state.unregistered.add(recipient.getId());
            }
        } else if (processor.genericIoError()) {
            state.retries.add(recipient.getId());
        } else {
            Log.w(TAG, "Failed to retrieve profile for " + recipient.getId());
        }
        return state;
    }).lastOrError().blockingGet();
    stopwatch.split("responses");
    Set<RecipientId> success = SetUtil.difference(recipientIds, operationState.retries);
    Map<RecipientId, ServiceId> newlyRegistered = Stream.of(operationState.profiles).map(Pair::first).filterNot(Recipient::isRegistered).collect(Collectors.toMap(Recipient::getId, r -> r.getServiceId().orNull()));
    // noinspection SimplifyStreamApiCallChains
    Util.chunk(operationState.profiles, 150).stream().forEach(list -> {
        SignalDatabase.runInTransaction(() -> {
            for (Pair<Recipient, ProfileAndCredential> profile : list) {
                process(profile.first(), profile.second());
            }
        });
    });
    recipientDatabase.markProfilesFetched(success, System.currentTimeMillis());
    if (operationState.unregistered.size() > 0 || newlyRegistered.size() > 0) {
        Log.i(TAG, "Marking " + newlyRegistered.size() + " users as registered and " + operationState.unregistered.size() + " users as unregistered.");
        recipientDatabase.bulkUpdatedRegisteredStatus(newlyRegistered, operationState.unregistered);
    }
    stopwatch.split("process");
    for (Pair<Recipient, ProfileAndCredential> profile : operationState.profiles) {
        setIdentityKey(profile.first(), profile.second().getProfile().getIdentityKey());
    }
    stopwatch.split("identityKeys");
    long keyCount = Stream.of(operationState.profiles).map(Pair::first).map(Recipient::getProfileKey).withoutNulls().count();
    Log.d(TAG, String.format(Locale.US, "Started with %d recipient(s). Found %d profile(s), and had keys for %d of them. Will retry %d.", recipients.size(), operationState.profiles.size(), keyCount, operationState.retries.size()));
    stopwatch.stop(TAG);
    recipientIds.clear();
    recipientIds.addAll(operationState.retries);
    if (recipientIds.size() > 0) {
        throw new RetryLaterException();
    }
}
Also used : SignalStore(org.thoughtcrime.securesms.keyvalue.SignalStore) NonNull(androidx.annotation.NonNull) Data(org.thoughtcrime.securesms.jobmanager.Data) JobManager(org.thoughtcrime.securesms.jobmanager.JobManager) RecipientUtil(org.thoughtcrime.securesms.recipients.RecipientUtil) ProfileKey(org.signal.zkgroup.profiles.ProfileKey) ProfileCipher(org.whispersystems.signalservice.api.crypto.ProfileCipher) ProfileAndCredential(org.whispersystems.signalservice.api.profiles.ProfileAndCredential) Badges(org.thoughtcrime.securesms.badges.Badges) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Locale(java.util.Locale) Map(java.util.Map) Badge(org.thoughtcrime.securesms.badges.models.Badge) Recipient(org.thoughtcrime.securesms.recipients.Recipient) InvalidCiphertextException(org.whispersystems.signalservice.api.crypto.InvalidCiphertextException) SignalExecutors(org.signal.core.util.concurrent.SignalExecutors) UnidentifiedAccessMode(org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode) Base64(org.thoughtcrime.securesms.util.Base64) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) ProfileKeyUtil(org.thoughtcrime.securesms.crypto.ProfileKeyUtil) Set(java.util.Set) SetUtil(org.thoughtcrime.securesms.util.SetUtil) GroupDatabase(org.thoughtcrime.securesms.database.GroupDatabase) IdentityKey(org.whispersystems.libsignal.IdentityKey) Log(org.signal.core.util.logging.Log) List(java.util.List) Nullable(androidx.annotation.Nullable) ProfileService(org.whispersystems.signalservice.api.services.ProfileService) Application(android.app.Application) Job(org.thoughtcrime.securesms.jobmanager.Job) Context(android.content.Context) SignalDatabase(org.thoughtcrime.securesms.database.SignalDatabase) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException) Stream(com.annimon.stream.Stream) Util(org.thoughtcrime.securesms.util.Util) ProfileName(org.thoughtcrime.securesms.profiles.ProfileName) WorkerThread(androidx.annotation.WorkerThread) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) InvalidKeyException(org.whispersystems.libsignal.InvalidKeyException) SignalServiceProfile(org.whispersystems.signalservice.api.profiles.SignalServiceProfile) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) Schedulers(io.reactivex.rxjava3.schedulers.Schedulers) Pair(org.whispersystems.libsignal.util.Pair) ProfileUtil(org.thoughtcrime.securesms.util.ProfileUtil) Observable(io.reactivex.rxjava3.core.Observable) IdentityUtil(org.thoughtcrime.securesms.util.IdentityUtil) Collectors(com.annimon.stream.Collectors) ProfileKeyCredential(org.signal.zkgroup.profiles.ProfileKeyCredential) TextUtils(android.text.TextUtils) NetworkConstraint(org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint) IOException(java.io.IOException) ServiceResponse(org.whispersystems.signalservice.internal.ServiceResponse) Optional(org.whispersystems.libsignal.util.guava.Optional) TimeUnit(java.util.concurrent.TimeUnit) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) ServiceId(org.whispersystems.signalservice.api.push.ServiceId) Collections(java.util.Collections) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) ProfileAndCredential(org.whispersystems.signalservice.api.profiles.ProfileAndCredential) Recipient(org.thoughtcrime.securesms.recipients.Recipient) Observable(io.reactivex.rxjava3.core.Observable) ServiceId(org.whispersystems.signalservice.api.push.ServiceId) RecipientDatabase(org.thoughtcrime.securesms.database.RecipientDatabase) ServiceResponse(org.whispersystems.signalservice.internal.ServiceResponse) ProfileService(org.whispersystems.signalservice.api.services.ProfileService) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException) Pair(org.whispersystems.libsignal.util.Pair)

Aggregations

Stopwatch (org.thoughtcrime.securesms.util.Stopwatch)42 NonNull (androidx.annotation.NonNull)20 IOException (java.io.IOException)16 Nullable (androidx.annotation.Nullable)14 Context (android.content.Context)12 ArrayList (java.util.ArrayList)12 Log (org.signal.core.util.logging.Log)12 WorkerThread (androidx.annotation.WorkerThread)10 List (java.util.List)10 Recipient (org.thoughtcrime.securesms.recipients.Recipient)10 RecipientId (org.thoughtcrime.securesms.recipients.RecipientId)10 Util (org.thoughtcrime.securesms.util.Util)10 Stream (com.annimon.stream.Stream)8 Collections (java.util.Collections)8 LinkedList (java.util.LinkedList)8 Set (java.util.Set)8 ApplicationDependencies (org.thoughtcrime.securesms.dependencies.ApplicationDependencies)8 SignalStore (org.thoughtcrime.securesms.keyvalue.SignalStore)8 Optional (org.whispersystems.libsignal.util.guava.Optional)8 Cursor (android.database.Cursor)6