Signal-Android/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource....

218 wiersze
7.3 KiB
Java

package org.thoughtcrime.securesms.conversationlist;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.signal.core.util.logging.Log;
import org.signal.paging.PagedDataSource;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.conversationlist.model.ConversationReader;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
abstract class ConversationListDataSource implements PagedDataSource<Long, Conversation> {
private static final String TAG = Log.tag(ConversationListDataSource.class);
protected final ThreadDatabase threadDatabase;
protected ConversationListDataSource(@NonNull Context context) {
this.threadDatabase = SignalDatabase.threads();
}
public static ConversationListDataSource create(@NonNull Context context, boolean isArchived) {
if (!isArchived) return new UnarchivedConversationListDataSource(context);
else return new ArchivedConversationListDataSource(context);
}
@Override
public int size() {
long startTime = System.currentTimeMillis();
int count = getTotalCount();
Log.d(TAG, "[size(), " + getClass().getSimpleName() + "] " + (System.currentTimeMillis() - startTime) + " ms");
return count;
}
@Override
public @NonNull List<Conversation> load(int start, int length, @NonNull CancellationSignal cancellationSignal) {
Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), " + getClass().getSimpleName());
List<Conversation> conversations = new ArrayList<>(length);
List<Recipient> recipients = new LinkedList<>();
Set<RecipientId> needsResolve = new HashSet<>();
try (ConversationReader reader = new ConversationReader(getCursor(start, length))) {
ThreadRecord record;
while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) {
conversations.add(new Conversation(record));
recipients.add(record.getRecipient());
needsResolve.add(record.getGroupMessageSender());
if (!record.getRecipient().isPushV2Group()) {
needsResolve.add(record.getRecipient().getId());
} else if (SmsDatabase.Types.isGroupUpdate(record.getType())) {
UpdateDescription description = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), record.getBody(), null);
needsResolve.addAll(description.getMentioned().stream().map(sid -> RecipientId.from(sid, null)).collect(Collectors.toList()));
}
}
}
stopwatch.split("cursor");
ApplicationDependencies.getRecipientCache().addToCache(recipients);
stopwatch.split("cache-recipients");
Recipient.resolvedList(needsResolve);
stopwatch.split("recipient-resolve");
stopwatch.stop(TAG);
return conversations;
}
@Override
public @Nullable Conversation load(Long threadId) {
throw new UnsupportedOperationException("Not implemented!");
}
@Override
public @NonNull Long getKey(@NonNull Conversation conversation) {
return conversation.getThreadRecord().getThreadId();
}
protected abstract int getTotalCount();
protected abstract Cursor getCursor(long offset, long limit);
private static class ArchivedConversationListDataSource extends ConversationListDataSource {
ArchivedConversationListDataSource(@NonNull Context context) {
super(context);
}
@Override
protected int getTotalCount() {
return threadDatabase.getArchivedConversationListCount();
}
@Override
protected Cursor getCursor(long offset, long limit) {
return threadDatabase.getArchivedConversationList(offset, limit);
}
}
@VisibleForTesting
static class UnarchivedConversationListDataSource extends ConversationListDataSource {
private int totalCount;
private int pinnedCount;
private int archivedCount;
private int unpinnedCount;
UnarchivedConversationListDataSource(@NonNull Context context) {
super(context);
}
@Override
protected int getTotalCount() {
int unarchivedCount = threadDatabase.getUnarchivedConversationListCount();
pinnedCount = threadDatabase.getPinnedConversationListCount();
archivedCount = threadDatabase.getArchivedConversationListCount();
unpinnedCount = unarchivedCount - pinnedCount;
totalCount = unarchivedCount;
if (archivedCount != 0) {
totalCount++;
}
if (pinnedCount != 0) {
if (unpinnedCount != 0) {
totalCount += 2;
} else {
totalCount += 1;
}
}
return totalCount;
}
@Override
protected Cursor getCursor(long offset, long limit) {
List<Cursor> cursors = new ArrayList<>(5);
long originalLimit = limit;
if (offset == 0 && hasPinnedHeader()) {
MatrixCursor pinnedHeaderCursor = new MatrixCursor(ConversationReader.HEADER_COLUMN);
pinnedHeaderCursor.addRow(ConversationReader.PINNED_HEADER);
cursors.add(pinnedHeaderCursor);
limit--;
}
Cursor pinnedCursor = threadDatabase.getUnarchivedConversationList(true, offset, limit);
cursors.add(pinnedCursor);
limit -= pinnedCursor.getCount();
if (offset == 0 && hasUnpinnedHeader()) {
MatrixCursor unpinnedHeaderCursor = new MatrixCursor(ConversationReader.HEADER_COLUMN);
unpinnedHeaderCursor.addRow(ConversationReader.UNPINNED_HEADER);
cursors.add(unpinnedHeaderCursor);
limit--;
}
long unpinnedOffset = Math.max(0, offset - pinnedCount - getHeaderOffset());
Cursor unpinnedCursor = threadDatabase.getUnarchivedConversationList(false, unpinnedOffset, limit);
cursors.add(unpinnedCursor);
if (offset + originalLimit >= totalCount && hasArchivedFooter()) {
MatrixCursor archivedFooterCursor = new MatrixCursor(ConversationReader.ARCHIVED_COLUMNS);
archivedFooterCursor.addRow(ConversationReader.createArchivedFooterRow(archivedCount));
cursors.add(archivedFooterCursor);
}
return new MergeCursor(cursors.toArray(new Cursor[]{}));
}
@VisibleForTesting
int getHeaderOffset() {
return (hasPinnedHeader() ? 1 : 0) + (hasUnpinnedHeader() ? 1 : 0);
}
@VisibleForTesting
boolean hasPinnedHeader() {
return pinnedCount != 0;
}
@VisibleForTesting
boolean hasUnpinnedHeader() {
return hasPinnedHeader() && unpinnedCount != 0;
}
@VisibleForTesting
boolean hasArchivedFooter() {
return archivedCount != 0;
}
}
}