diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java new file mode 100644 index 0000000000..1fa4c31ed1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.java @@ -0,0 +1,82 @@ +package org.thoughtcrime.securesms.conversation; + +import android.database.Cursor; + +import androidx.annotation.NonNull; + +public final class ConversationData { + private final Cursor cursor; + private final int offset; + private final int limit; + private final long lastSeen; + private final int previousOffset; + private final boolean firstLoad; + private final boolean hasSent; + private final boolean isMessageRequestAccepted; + private final boolean hasPreMessageRequestMessages; + + public ConversationData(Cursor cursor, + int offset, + int limit, + long lastSeen, + int previousOffset, + boolean firstLoad, + boolean hasSent, + boolean isMessageRequestAccepted, + boolean hasPreMessageRequestMessages) + { + this.cursor = cursor; + this.offset = offset; + this.limit = limit; + this.lastSeen = lastSeen; + this.previousOffset = previousOffset; + this.firstLoad = firstLoad; + this.hasSent = hasSent; + this.isMessageRequestAccepted = isMessageRequestAccepted; + this.hasPreMessageRequestMessages = hasPreMessageRequestMessages; + } + + public @NonNull Cursor getCursor() { + return cursor; + } + + public boolean hasLimit() { + return limit > 0; + } + + public int getLimit() { + return limit; + } + + public boolean hasOffset() { + return offset > 0; + } + + public int getOffset() { + return offset; + } + + public int getPreviousOffset() { + return previousOffset; + } + + public long getLastSeen() { + return lastSeen; + } + + public boolean isFirstLoad() { + return firstLoad; + } + + public boolean hasSent() { + return hasSent; + } + + public boolean isMessageRequestAccepted() { + return isMessageRequestAccepted; + } + + public boolean hasPreMessageRequestMessages() { + return hasPreMessageRequestMessages; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index f34b210ebc..4260907b6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -51,6 +51,7 @@ import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.text.HtmlCompat; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProviders; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; @@ -81,7 +82,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.database.loaders.ConversationLoader; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; @@ -135,9 +135,7 @@ import java.util.Locale; import java.util.Set; @SuppressLint("StaticFieldLeak") -public class ConversationFragment extends Fragment - implements LoaderManager.LoaderCallbacks -{ +public class ConversationFragment extends Fragment { private static final String TAG = ConversationFragment.class.getSimpleName(); private static final String KEY_LIMIT = "limit"; @@ -152,11 +150,6 @@ public class ConversationFragment extends Fragment private LiveRecipient recipient; private long threadId; - private long lastSeen; - private int startingPosition; - private int previousOffset; - private int activeOffset; - private boolean firstLoad; private boolean isReacting; private ActionMode actionMode; private Locale locale; @@ -172,6 +165,7 @@ public class ConversationFragment extends Fragment private ConversationBannerView conversationBanner; private ConversationBannerView emptyConversationBanner; private MessageRequestViewModel messageRequestViewModel; + private ConversationViewModel conversationViewModel; @Override public void onCreate(Bundle icicle) { @@ -214,6 +208,9 @@ public class ConversationFragment extends Fragment setupListLayoutListeners(); + this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); + conversationViewModel.getConversation().observe(this, this::presentConversation); + return view; } @@ -295,16 +292,16 @@ public class ConversationFragment extends Fragment initializeListAdapter(); if (threadId == -1) { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); + conversationViewModel.refreshConversation(); } } public void reloadList() { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); + conversationViewModel.refreshConversation(); } public void moveToLastSeen() { - if (lastSeen <= 0) { + if (conversationViewModel.getLastSeen() <= 0) { Log.i(TAG, "No need to move to last seen."); return; } @@ -314,7 +311,7 @@ public class ConversationFragment extends Fragment return; } - int position = getListAdapter().findLastSeenPosition(lastSeen); + int position = getListAdapter().findLastSeenPosition(conversationViewModel.getLastSeen()); scrollToLastSeenPosition(position); } @@ -403,13 +400,17 @@ public class ConversationFragment extends Fragment private void initializeResources() { long oldThreadId = threadId; + long lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); + int startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); + int limit = getArguments() != null ? getArguments().getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT) : PARTIAL_CONVERSATION_LIMIT; + this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA)); this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); - this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); - this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); - this.firstLoad = true; this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId); + + conversationViewModel.onConversationDataAvailable(recipient.get(), threadId, lastSeen, startingPosition, limit); + OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); list.addOnScrollListener(scrollListener); @@ -425,8 +426,7 @@ public class ConversationFragment extends Fragment list.setAdapter(adapter); list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); - setLastSeen(lastSeen); - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); + setLastSeen(conversationViewModel.getLastSeen()); emptyConversationBanner.setVisibility(View.GONE); } else if (FeatureFlags.messageRequests() && threadId == -1) { @@ -436,9 +436,7 @@ public class ConversationFragment extends Fragment private void initializeLoadMoreView(ViewSwitcher loadMoreView) { loadMoreView.setOnClickListener(v -> { - Bundle args = new Bundle(); - args.putInt(KEY_LIMIT, 0); - getLoaderManager().restartLoader(0, args, ConversationFragment.this); + conversationViewModel.onLoadMoreClicked(); loadMoreView.showNext(); loadMoreView.setOnClickListener(null); }); @@ -565,7 +563,8 @@ public class ConversationFragment extends Fragment } public void setLastSeen(long lastSeen) { - this.lastSeen = lastSeen; + conversationViewModel.onLastSeenChanged(lastSeen); + if (lastSeenDecoration != null) { list.removeItemDecoration(lastSeenDecoration); } @@ -827,109 +826,12 @@ public class ConversationFragment extends Fragment }); } - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - Log.i(TAG, "onCreateLoader"); - - int limit = args.getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT); - int offset = 0; - if (limit != 0 && startingPosition >= limit) { - offset = Math.max(startingPosition - (limit / 2) + 1, 0); - startingPosition -= offset - 1; - } - - return new ConversationLoader(getActivity(), threadId, offset, limit, lastSeen); - } - - @Override - public void onLoadFinished(@NonNull Loader cursorLoader, Cursor cursor) { - int count = cursor.getCount(); - ConversationLoader loader = (ConversationLoader) cursorLoader; - - ConversationAdapter adapter = getListAdapter(); - if (adapter == null) { - return; - } - - if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) { - adapter.setFooterView(topLoadMoreView); - } else if (FeatureFlags.messageRequests()) { - adapter.setFooterView(conversationBanner); - } else { - adapter.setFooterView(null); - } - - if (lastSeen == -1) { - setLastSeen(loader.getLastSeen()); - } - - if (FeatureFlags.messageRequests() && !loader.hasPreMessageRequestMessages()) { - clearHeaderIfNotTyping(adapter); - } else { - if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { - adapter.setHeaderView(unknownSenderView); - } else { - clearHeaderIfNotTyping(adapter); - } - } - - if (loader.hasOffset()) { - adapter.setHeaderView(bottomLoadMoreView); - } - - if (firstLoad || loader.hasOffset()) { - previousOffset = loader.getOffset(); - } - - activeOffset = loader.getOffset(); - adapter.changeCursor(cursor); - listener.onCursorChanged(); - - int lastSeenPosition = adapter.findLastSeenPosition(lastSeen); - - if (isTypingIndicatorShowing()) { - lastSeenPosition = Math.max(lastSeenPosition - 1, 0); - } - - if (firstLoad) { - if (startingPosition >= 0) { - scrollToStartingPosition(startingPosition); - } else if (loader.isMessageRequestAccepted()) { - scrollToLastSeenPosition(lastSeenPosition); - } else if (FeatureFlags.messageRequests()) { - list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1)); - } - firstLoad = false; - } else if (previousOffset > 0) { - int scrollPosition = previousOffset + getListLayoutManager().findFirstVisibleItemPosition(); - scrollPosition = Math.min(scrollPosition, count - 1); - - View firstView = list.getLayoutManager().getChildAt(scrollPosition); - int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); - - getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset); - previousOffset = 0; - } - - if (lastSeenPosition <= 0) { - setLastSeen(0); - } - } - private void clearHeaderIfNotTyping(ConversationAdapter adapter) { if (adapter.getHeaderView() != typingView) { adapter.setHeaderView(null); } } - @Override - public void onLoaderReset(@NonNull Loader arg0) { - if (list.getAdapter() != null) { - getListAdapter().changeCursor(null); - listener.onCursorChanged(); - } - } - public long stageOutgoingMessage(OutgoingMediaMessage message) { MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); @@ -960,6 +862,73 @@ public class ConversationFragment extends Fragment } } + private void presentConversation(@NonNull ConversationData conversation) { + Cursor cursor = conversation.getCursor(); + int count = cursor.getCount(); + + ConversationAdapter adapter = getListAdapter(); + if (adapter == null) { + return; + } + + if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && conversation.hasLimit()) { + adapter.setFooterView(topLoadMoreView); + } else if (FeatureFlags.messageRequests()) { + adapter.setFooterView(conversationBanner); + } else { + adapter.setFooterView(null); + } + + if (conversationViewModel.getLastSeen() == -1) { + setLastSeen(conversation.getLastSeen()); + } + + if (FeatureFlags.messageRequests() && !conversation.hasPreMessageRequestMessages()) { + clearHeaderIfNotTyping(adapter); + } else { + if (!conversation.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { + adapter.setHeaderView(unknownSenderView); + } else { + clearHeaderIfNotTyping(adapter); + } + } + + if (conversation.hasOffset()) { + adapter.setHeaderView(bottomLoadMoreView); + } + + adapter.changeCursor(cursor); + listener.onCursorChanged(); + + int lastSeenPosition = adapter.findLastSeenPosition(conversationViewModel.getLastSeen()); + + if (isTypingIndicatorShowing()) { + lastSeenPosition = Math.max(lastSeenPosition - 1, 0); + } + + if (conversation.isFirstLoad()) { + if (conversationViewModel.getStartingPosition() >= 0) { + scrollToStartingPosition(conversationViewModel.getStartingPosition()); + } else if (conversation.isMessageRequestAccepted()) { + scrollToLastSeenPosition(lastSeenPosition); + } else if (FeatureFlags.messageRequests()) { + list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1)); + } + } else if (conversation.getPreviousOffset() > 0) { + int scrollPosition = conversation.getPreviousOffset() + getListLayoutManager().findFirstVisibleItemPosition(); + scrollPosition = Math.min(scrollPosition, count - 1); + + View firstView = list.getLayoutManager().getChildAt(scrollPosition); + int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); + + getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset); + } + + if (lastSeenPosition <= 0) { + setLastSeen(0); + } + } + private void scrollToStartingPosition(final int startingPosition) { list.post(() -> { list.getLayoutManager().scrollToPosition(startingPosition); @@ -1004,6 +973,8 @@ public class ConversationFragment extends Fragment } private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) { + int activeOffset = conversationViewModel.getActiveOffset(); + Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount()); if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) { @@ -1018,9 +989,7 @@ public class ConversationFragment extends Fragment } else { Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader."); - firstLoad = true; - startingPosition = position; - getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this); + conversationViewModel.onMoveJumpToMessageOutOfRange(position); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java new file mode 100644 index 0000000000..c2f1734408 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -0,0 +1,57 @@ +package org.thoughtcrime.securesms.conversation; + +import android.content.Context; +import android.database.Cursor; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.recipients.RecipientUtil; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; +import org.whispersystems.libsignal.util.Pair; + +import java.util.concurrent.Executor; + +public class ConversationRepository { + + private final Context context; + private final Executor executor; + + public ConversationRepository() { + this.context = ApplicationDependencies.getApplication(); + this.executor = SignalExecutors.BOUNDED; + } + + public void getConversationData(long threadId, + int offset, + int limit, + long lastSeen, + int previousOffset, + boolean firstLoad, + @NonNull Callback callback) + { + executor.execute(() -> callback.onComplete(getConversationDataInternal(threadId, offset, limit, lastSeen, previousOffset, firstLoad))); + } + + private @NonNull ConversationData getConversationDataInternal(long threadId, int offset, int limit, long lastSeen, int previousOffset, boolean firstLoad) { + Pair lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId); + + boolean hasSent = lastSeenAndHasSent.second(); + + if (lastSeen == -1) { + lastSeen = lastSeenAndHasSent.first(); + } + + boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId); + boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId); + Cursor cursor = DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, offset, limit); + + return new ConversationData(cursor, offset, limit, lastSeen, previousOffset, firstLoad, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages); + } + + + interface Callback { + void onComplete(@NonNull E result); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java index 2fe31aa14b..18aadb1b0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java @@ -1,6 +1,11 @@ package org.thoughtcrime.securesms.conversation; +import android.app.Application; import android.content.Context; +import android.database.ContentObservable; +import android.database.ContentObserver; +import android.database.Cursor; +import android.os.Handler; import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; @@ -8,32 +13,139 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; +import org.thoughtcrime.securesms.database.DatabaseContentProviders; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaRepository; +import org.thoughtcrime.securesms.pin.PinState; +import org.thoughtcrime.securesms.recipients.Recipient; import java.util.List; class ConversationViewModel extends ViewModel { - private final Context context; - private final MediaRepository mediaRepository; - private final MutableLiveData> recentMedia; + private static final String TAG = Log.tag(ConversationViewModel.class); + + private static final int NO_LIMIT = 0; + + private final Application context; + private final MediaRepository mediaRepository; + private final ConversationRepository conversationRepository; + private final MutableLiveData> recentMedia; + private final MutableLiveData conversation; + private final ContentObserver contentObserver; + + private Recipient recipient; + private long threadId; + private boolean firstLoad; + private int requestedLimit; + private long lastSeen; + private int startingPosition; + private int previousOffset; + private boolean contentObserverRegistered; private ConversationViewModel() { - this.context = ApplicationDependencies.getApplication(); - this.mediaRepository = new MediaRepository(); - this.recentMedia = new MutableLiveData<>(); + this.context = ApplicationDependencies.getApplication(); + this.mediaRepository = new MediaRepository(); + this.conversationRepository = new ConversationRepository(); + this.recentMedia = new MutableLiveData<>(); + this.conversation = new MutableLiveData<>(); + this.contentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + ConversationData data = conversation.getValue(); + if (data != null) { + conversationRepository.getConversationData(threadId, data.getOffset(), data.getLimit(), data.getLastSeen(), data.getPreviousOffset(), data.isFirstLoad(), conversation::postValue); + } else { + Log.w(TAG, "Got a content change, but have no previous data?"); + } + } + }; } void onAttachmentKeyboardOpen() { mediaRepository.getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, recentMedia::postValue); } + void onConversationDataAvailable(Recipient recipient, long threadId, long lastSeen, int startingPosition, int limit) { + this.recipient = recipient; + this.threadId = threadId; + this.lastSeen = lastSeen; + this.startingPosition = startingPosition; + this.requestedLimit = limit; + this.firstLoad = true; + + if (!contentObserverRegistered) { + context.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadId), true, contentObserver); + contentObserverRegistered = true; + } + + refreshConversation(); + } + + void refreshConversation() { + int limit = requestedLimit; + int offset = 0; + + if (requestedLimit != NO_LIMIT && startingPosition >= requestedLimit) { + offset = Math.max(startingPosition - (requestedLimit / 2) + 1, 0); + startingPosition -= offset - 1; + } + + conversationRepository.getConversationData(threadId, offset, limit, lastSeen, previousOffset, firstLoad, conversation::postValue); + + if (firstLoad) { + firstLoad = false; + } + + previousOffset = offset; + } + + void onLoadMoreClicked() { + requestedLimit = 0; + refreshConversation(); + } + + void onMoveJumpToMessageOutOfRange(int startingPosition) { + this.firstLoad = true; + this.startingPosition = startingPosition; + + refreshConversation(); + } + + void onLastSeenChanged(long lastSeen) { + this.lastSeen = lastSeen; + } + @NonNull LiveData> getRecentMedia() { return recentMedia; } + @NonNull LiveData getConversation() { + return conversation; + } + + long getLastSeen() { + return lastSeen; + } + + int getStartingPosition() { + return startingPosition; + } + + int getActiveOffset() { + ConversationData data = conversation.getValue(); + return data != null ? data.getOffset() : 0; + } + + @Override + protected void onCleared() { + context.getContentResolver().unregisterContentObserver(contentObserver); + contentObserverRegistered = false; + } + static class Factory extends ViewModelProvider.NewInstanceFactory { @Override public @NonNull T create(@NonNull Class modelClass) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java deleted file mode 100644 index 9cddaf700d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.thoughtcrime.securesms.database.loaders; - -import android.content.Context; -import android.database.Cursor; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.AbstractCursorLoader; -import org.whispersystems.libsignal.util.Pair; - -public class ConversationLoader extends AbstractCursorLoader { - private final long threadId; - private int offset; - private int limit; - private long lastSeen; - private boolean hasSent; - private boolean isMessageRequestAccepted; - private boolean hasPreMessageRequestMessages; - - public ConversationLoader(Context context, long threadId, int offset, int limit, long lastSeen) { - super(context); - this.threadId = threadId; - this.offset = offset; - this.limit = limit; - this.lastSeen = lastSeen; - this.hasSent = true; - } - - public boolean hasLimit() { - return limit > 0; - } - - public boolean hasOffset() { - return offset > 0; - } - - public int getOffset() { - return offset; - } - - public long getLastSeen() { - return lastSeen; - } - - public boolean hasSent() { - return hasSent; - } - - public boolean isMessageRequestAccepted() { - return isMessageRequestAccepted; - } - - public boolean hasPreMessageRequestMessages() { - return hasPreMessageRequestMessages; - } - - @Override - public Cursor getCursor() { - Pair lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId); - - this.hasSent = lastSeenAndHasSent.second(); - - if (lastSeen == -1) { - this.lastSeen = lastSeenAndHasSent.first(); - } - - this.isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId); - this.hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId); - - return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, offset, limit); - } -}