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());
});
}
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;
}
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);
}
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;
}
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();
}
}
Aggregations