聊天(二)—通知消息

当对方的手机号重新在新设备登录时(或者对方登出后又重新登入时),会话页面会接收到如下的信息:
在这里插入图片描述
显示的逻辑是在

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

...


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值