当对方的手机号重新在新设备登录时(或者对方登出后又重新登入时),会话页面会接收到如下的信息:
显示的逻辑是在
//MessageRecord.java
...
@Override
public SpannableString getDisplayBody(@NonNull Context context) {
...
return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().getDisplayName(context)));
...
什么时候进行获取与更新联系人数据的?
//StorageSyncJob.java
...
@Override
protected void onRun() throws IOException, RetryLaterException {
if (!SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) {
Log.i(TAG, "Doesn't have a PIN. Skipping.");
return;
}
if (!TextSecurePreferences.isPushRegistered(context)) {
Log.i(TAG, "Not registered. Skipping.");
return;
}
try {
//执行 performSync()
boolean needsMultiDeviceSync = performSync();
if (TextSecurePreferences.isMultiDevice(context) && needsMultiDeviceSync) {
ApplicationDependencies.getJobManager().add(new MultiDeviceStorageSyncRequestJob());
}
SignalStore.storageService().onSyncCompleted();
} catch (InvalidKeyException e) {
Log.w(TAG, "Failed to decrypt remote storage! Force-pushing and syncing the storage key to linked devices.", e);
ApplicationDependencies.getJobManager().startChain(new MultiDeviceKeysUpdateJob())
.then(new StorageForcePushJob())
.then(new MultiDeviceStorageSyncRequestJob())
.enqueue();
}
}
private boolean performSync() throws IOException, RetryLaterException, InvalidKeyException {
...
try {
self = Recipient.self().fresh();
//执行process,remoteContacts是从远端获取到的联系人信息
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);
new AccountRecordProcessor(context, self).process(remoteAccount, StorageSyncHelper.KEY_GENERATOR);
...
//DefaultStorageRecordProcessor.java
@Override
public void process(@NonNull Collection<E> remoteRecords, @NonNull StorageKeyGenerator keyGenerator) throws IOException {
Set<E> matchedRecords = new TreeSet<>(this);
int i = 0;
for (E remote : remoteRecords) {
if (isInvalid(remote)) {
warn(i, remote, "Found invalid key! Ignoring it.");
} else {
Optional<E> local = getMatching(remote, keyGenerator);
if (local.isPresent()) {
//将远端获取的和本地的进行合并
E merged = merge(remote, local.get(), keyGenerator);
if (matchedRecords.contains(local.get())) {
warn(i, remote, "Multiple remote records map to the same local record! Ignoring this one.");
} else {
matchedRecords.add(local.get());
if (!merged.equals(remote)) {
info(i, remote, "[Remote Update] " + new StorageRecordUpdate<>(remote, merged).toString());
}
if (!merged.equals(local.get())) {
StorageRecordUpdate<E> update = new StorageRecordUpdate<>(local.get(), merged);
info(i, remote, "[Local Update] " + update.toString());
//如果合并后的结果与本地不一样,说明远端数据更新了,则执行updateLocal()进行更新本地的数据
updateLocal(update);
}
}
} else {
info(i, remote, "No matching local record. Inserting.");
insertLocal(remote);
}
}
i++;
}
}
看下ContactRecordProcessor的updateLocal():
//ContactRecordProcessor.java
public class ContactRecordProcessor extends DefaultStorageRecordProcessor<SignalContactRecord> {
...
@Override
void updateLocal(@NonNull StorageRecordUpdate<SignalContactRecord> update) {
recipientDatabase.applyStorageSyncContactUpdate(update);
}
...
调用RecipientDatabase的applyStorageSyncContactInsert():
//RecipientDatabase.java
public void applyStorageSyncContactInsert(@NonNull SignalContactRecord insert) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
ContentValues values = getValuesForStorageContact(insert, true);
long id = db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
RecipientId recipientId = null;
if (id < 0) {
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.");
recipientId = getAndPossiblyMerge(insert.getAddress().getUuid().get(), insert.getAddress().getNumber().get(), true);
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId));
} else {
recipientId = RecipientId.from(id);
}
if (insert.getIdentityKey().isPresent()) {
try {
IdentityKey identityKey = new IdentityKey(insert.getIdentityKey().get(), 0);
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
} catch (InvalidKeyException e) {
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e);
}
}
threadDatabase.applyStorageSyncUpdate(recipientId, insert);
}
调用IdentityDatabase的updateIdentityAfterSync()
//IdentityDatabase.java
public void updateIdentityAfterSync(@NonNull RecipientId id, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
boolean hadEntry = getIdentity(id).isPresent();
boolean keyMatches = hasMatchingKey(id, identityKey);
boolean statusMatches = keyMatches && hasMatchingStatus(id, identityKey, verifiedStatus);
if (!keyMatches || !statusMatches) {
saveIdentityInternal(id, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
Optional<IdentityRecord> record = getIdentity(id);
if (record.isPresent()) EventBus.getDefault().post(record.get());
}
if (hadEntry && !keyMatches) {
IdentityUtil.markIdentityUpdate(context, id);
}
}
//IdentityUtil.java
public static void markIdentityUpdate(@NonNull Context context, @NonNull RecipientId recipientId) {
long time = System.currentTimeMillis();
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
try (GroupDatabase.Reader reader = groupDatabase.getGroups()) {
GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.getMembers().contains(recipientId) && groupRecord.isActive()) {
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, time, null, Optional.of(groupRecord.getId()), 0, false);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
smsDatabase.insertMessageInbox(groupUpdate);
}
}
}
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, -1, null, Optional.absent(), 0, false);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId());
}
}
这里会创建一个IncomingIdentityUpdateMessage消息,然后发送给UI,UI收到消息后进行显示,就是前面我们看到的提示消息。
总结:
app后台在特定场景下会执行StorageSyncJob任务,该任务会去获取远端的联系人信息,app客户端获取到远端的联系人信息后,将数据与本地的进行比对,如果不一样,则显示 Your safety number with ... has changed
的提示消息。
后台会执行StorageSyncJob的场景有很多,下面是其中一个调用StorageSyncJob的场景
//StorageSyncHelper.java
public static void scheduleSyncForDataChange() {
if (!SignalStore.registrationValues().isRegistrationComplete()) {
Log.d(TAG, "Registration still ongoing. Ignore sync request.");
return;
}
ApplicationDependencies.getJobManager().add(new StorageSyncJob());
}
查看StorageSyncJob在哪些地方被引用即可看到所有会提交StorageSyncJob任务给线程池执行的场景。
会话列表页面也会显示这种提示信息:
会话列表页面的消息主题
会话列表页面也会显示这种提示信息:
//ConversationListItem.java
private static SpannableString getThreadDisplayBody(@NonNull Context context, @NonNull ThreadRecord thread) {
...
} else if (SmsDatabase.Types.isIdentityUpdate(thread.getType())) {
...
return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, thread.getRecipient().getDisplayName(context)));
}
...
通知消息
通知消息的显示文本是根据MessageRecord的getDisplayBody方法生成的:
//DefaultMessageNotifier.java
private static NotificationState constructNotificationState(@NonNull Context context,
@NonNull Cursor cursor)
{
NotificationState notificationState = new NotificationState();
MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor);
MessageRecord record;
while ((record = reader.getNext()) != null) {
long id = record.getId();
boolean mms = record.isMms() || record.isMmsNotification();
Recipient recipient = record.getIndividualRecipient().resolve();
Recipient conversationRecipient = record.getRecipient().resolve();
long threadId = record.getThreadId();
CharSequence body = record.getDisplayBody(context);
...
//MessageRecord.java
...
@Override
public SpannableString getDisplayBody(@NonNull Context context) {
if (isGroupUpdate() && isGroupV2()) {
return new SpannableString(getGv2Description(context));
} else if (isGroupUpdate() && isOutgoing()) {
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
} else if (isGroupUpdate()) {
return new SpannableString(GroupUtil.getDescription(context, getBody(), false).toString(getIndividualRecipient()));
} else if (isGroupQuit() && isOutgoing()) {
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
} else if (isGroupQuit()) {
return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().getDisplayName(context)));
} else if (isIncomingCall()) {
return new SpannableString(context.getString(R.string.MessageRecord_s_called_you, getIndividualRecipient().getDisplayName(context)));
} else if (isOutgoingCall()) {
return new SpannableString(context.getString(R.string.MessageRecord_you_called));
} else if (isMissedCall()) {
return new SpannableString(context.getString(R.string.MessageRecord_missed_call));
} else if (isJoined()) {
return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().getDisplayName(context)));
} else if (isExpirationTimerUpdate()) {
int seconds = (int)(getExpiresIn() / 1000);
if (seconds <= 0) {
return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_disabled_disappearing_messages))
: new SpannableString(context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, getIndividualRecipient().getDisplayName(context)));
}
String time = ExpirationUtil.getExpirationDisplayValue(context, seconds);
return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time))
: new SpannableString(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().getDisplayName(context), time));
} else if (isIdentityUpdate()) {
return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().getDisplayName(context)));
} else if (isIdentityVerified()) {
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().getDisplayName(context)));
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().getDisplayName(context)));
} else if (isIdentityDefault()) {
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().getDisplayName(context)));
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().getDisplayName(context)));
}
return new SpannableString(getBody());
}
...