Convert all database notifiers to use DatabaseObserver.

Lots of red in this diff to celebrate the release of Red (Taylor's Version).
This commit is contained in:
Greyson Parrelli 2021-11-12 12:14:59 -05:00 committed by Cody Henthorne
parent ab55fec6bd
commit 658de3b6e7
30 changed files with 179 additions and 729 deletions

View file

@ -770,22 +770,6 @@
</provider>
<provider android:name=".database.DatabaseContentProviders$Conversation"
android:authorities="${applicationId}.database.conversation"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Attachment"
android:authorities="${applicationId}.database.attachment"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Sticker"
android:authorities="${applicationId}.database.sticker"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$StickerPack"
android:authorities="${applicationId}.database.stickerpack"
android:exported="false" />
<receiver android:name=".service.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>

View file

@ -568,26 +568,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
if (item == 0) {
viewPagerListener.onPageSelected(0);
}
cursor.registerContentObserver(new ContentObserver(new Handler(getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
onMediaChange();
}
});
} else {
mediaNotAvailable();
}
}
private void onMediaChange() {
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
if (adapter != null) {
adapter.checkMedia(mediaPager.getCurrentItem());
}
}
@Override
public void onLoaderReset(@NonNull Loader<Pair<Cursor, Integer>> loader) {
@ -701,11 +686,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
public boolean hasFragmentFor(int position) {
return mediaPreviewFragment != null;
}
@Override
public void checkMedia(int currentItem) {
}
}
private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) {
@ -824,14 +804,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
return mediaFragments.containsKey(position);
}
@Override
public void checkMedia(int position) {
MediaPreviewFragment fragment = mediaFragments.get(position);
if (fragment != null) {
fragment.checkMediaStillAvailable();
}
}
private int getCursorPosition(int position) {
if (leftIsRecent) return position;
else return cursor.getCount() - 1 - position;
@ -870,6 +842,5 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
void pause(int position);
@Nullable View getPlaybackControls(int position);
boolean hasFragmentFor(int position);
void checkMedia(int currentItem);
}
}

View file

@ -9,10 +9,10 @@ import androidx.lifecycle.MutableLiveData;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.search.MessageResult;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.search.SearchRepository;
import org.thoughtcrime.securesms.util.Debouncer;
import java.util.Collections;
import java.util.List;
public class ConversationSearchViewModel extends AndroidViewModel {
@ -39,7 +39,7 @@ public class ConversationSearchViewModel extends AndroidViewModel {
void onQueryUpdated(@NonNull String query, long threadId, boolean forced) {
if (firstSearch && query.length() < 2) {
result.postValue(new SearchResult(CursorList.emptyList(), 0));
result.postValue(new SearchResult(Collections.emptyList(), 0));
return;
}

View file

@ -1,9 +1,6 @@
package org.thoughtcrime.securesms.conversation;
import android.app.Application;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -12,39 +9,34 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.Throttler;
import java.util.Collections;
import java.util.List;
class ConversationStickerViewModel extends ViewModel {
private final Application application;
private final StickerSearchRepository repository;
private final MutableLiveData<List<StickerRecord>> stickers;
private final MutableLiveData<Boolean> stickersAvailable;
private final Throttler availabilityThrottler;
private final ContentObserver packObserver;
private final DatabaseObserver.Observer packObserver;
private ConversationStickerViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
this.stickers = new MutableLiveData<>();
this.stickersAvailable = new MutableLiveData<>();
this.availabilityThrottler = new Throttler(500);
this.packObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
}
this.packObserver = () -> {
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
};
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(packObserver);
}
@NonNull LiveData<List<StickerRecord>> getStickerResults() {
@ -58,7 +50,7 @@ class ConversationStickerViewModel extends ViewModel {
void onInputTextUpdated(@NonNull String text) {
if (TextUtils.isEmpty(text) || text.length() > EmojiSource.getLatest().getMaxEmojiLength()) {
stickers.setValue(CursorList.emptyList());
stickers.setValue(Collections.emptyList());
} else {
repository.searchByEmoji(text, stickers::postValue);
}
@ -66,7 +58,7 @@ class ConversationStickerViewModel extends ViewModel {
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(packObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(packObserver);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {

View file

@ -317,15 +317,6 @@ public class AttachmentDatabase extends Database {
return false;
}
public boolean hasAttachmentFilesForMessage(long mmsId) {
String selection = MMS_ID + " = ? AND (" + DATA + " NOT NULL OR " + TRANSFER_STATE + " != ?)";
String[] args = new String[] { String.valueOf(mmsId), String.valueOf(TRANSFER_PROGRESS_DONE) };
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
return cursor != null && cursor.moveToFirst();
}
}
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
final SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
final List<DatabaseAttachment> attachments = new LinkedList<>();
@ -343,8 +334,7 @@ public class AttachmentDatabase extends Database {
return attachments;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAttachmentsForMessage(long mmsId) {
public boolean deleteAttachmentsForMessage(long mmsId) {
Log.d(TAG, "[deleteAttachmentsForMessage] mmsId: " + mmsId);
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
@ -365,8 +355,10 @@ public class AttachmentDatabase extends Database {
cursor.close();
}
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId + ""});
int deleteCount = database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId + ""});
notifyAttachmentListeners();
return deleteCount > 0;
}
/**
@ -631,6 +623,7 @@ public class AttachmentDatabase extends Database {
notifyConversationListeners(threadId);
notifyConversationListListeners();
notifyAttachmentListeners();
}
if (transferFile != null) {
@ -1273,6 +1266,9 @@ public class AttachmentDatabase extends Database {
Log.d(TAG, "Inserting attachment for mms id: " + mmsId);
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
AttachmentId attachmentId = null;
boolean notifyPacks = false;
database.beginTransaction();
try {
DataInfo dataInfo = null;
@ -1347,20 +1343,23 @@ public class AttachmentDatabase extends Database {
}
}
boolean notifyPacks = attachment.isSticker() && !hasStickerAttachments();
long rowId = database.insert(TABLE_NAME, null, contentValues);
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
long rowId = database.insert(TABLE_NAME, null, contentValues);
if (notifyPacks) {
notifyStickerPackListeners();
}
attachmentId = new AttachmentId(rowId, uniqueId);
notifyPacks = attachment.isSticker() && !hasStickerAttachments();
database.setTransactionSuccessful();
return attachmentId;
} finally {
database.endTransaction();
}
if (notifyPacks) {
notifyStickerPackListeners();
}
notifyAttachmentListeners();
return attachmentId;
}
private @Nullable DatabaseAttachment findTemplateAttachment(@NonNull String dataHash) {

View file

@ -1,205 +0,0 @@
package org.thoughtcrime.securesms.database;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/**
* A list backed by a {@link Cursor} that retrieves models using a provided {@link ModelBuilder}.
* Allows you to abstract away the use of a {@link Cursor} while still getting the benefits of a
* {@link Cursor} (e.g. windowing).
*
* The one special consideration that must be made is that because this contains a cursor, you must
* call {@link #close()} when you are finished with it.
*
* Given that this is cursor-backed, it is effectively immutable.
*/
public class CursorList<T> implements List<T>, ObservableContent {
private final Cursor cursor;
private final ModelBuilder<T> modelBuilder;
public CursorList(@NonNull Cursor cursor, @NonNull ModelBuilder<T> modelBuilder) {
this.cursor = cursor;
this.modelBuilder = modelBuilder;
forceQueryLoad();
}
public static <T> CursorList<T> emptyList() {
//noinspection ConstantConditions,unchecked
return (CursorList<T>) new CursorList(emptyCursor(), null);
}
private static Cursor emptyCursor() {
return new MatrixCursor(new String[] { "a" }, 0);
}
@Override
public int size() {
return cursor.getCount();
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Iterator<T> iterator() {
return new Iterator<T>() {
int index = 0;
@Override
public boolean hasNext() {
return cursor.getCount() > 0 && !cursor.isLast();
}
@Override
public T next() {
cursor.moveToPosition(index++);
return modelBuilder.build(cursor);
}
};
}
@Override
public @NonNull Object[] toArray() {
Object[] out = new Object[size()];
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
out[i] = modelBuilder.build(cursor);
}
return out;
}
@Override
public boolean add(T o) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(@NonNull Collection collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int i, @NonNull Collection collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public T get(int i) {
cursor.moveToPosition(i);
return modelBuilder.build(cursor);
}
@Override
public T set(int i, T o) {
throw new UnsupportedOperationException();
}
@Override
public void add(int i, T o) {
throw new UnsupportedOperationException();
}
@Override
public T remove(int i) {
throw new UnsupportedOperationException();
}
@Override
public int indexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public @NonNull ListIterator<T> listIterator() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull ListIterator<T> listIterator(int i) {
throw new UnsupportedOperationException();
}
@Override
public @NonNull List<T> subList(int i, int i1) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NonNull Collection collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NonNull Collection collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NonNull Collection collection) {
throw new UnsupportedOperationException();
}
@Override
public @NonNull T[] toArray(@Nullable Object[] objects) {
throw new UnsupportedOperationException();
}
@Override
public void close() {
if (!cursor.isClosed()) {
cursor.close();
}
}
@Override
public void registerContentObserver(@NonNull ContentObserver observer) {
cursor.registerContentObserver(observer);
}
@Override
public void unregisterContentObserver(@NonNull ContentObserver observer) {
cursor.unregisterContentObserver(observer);
}
private void forceQueryLoad() {
cursor.getCount();
}
public interface ModelBuilder<T> {
T build(@NonNull Cursor cursor);
}
}

View file

@ -64,47 +64,16 @@ public abstract class Database {
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
}
protected void notifyStickerListeners() {
context.getContentResolver().notifyChange(DatabaseContentProviders.Sticker.CONTENT_URI, null);
}
protected void notifyStickerPackListeners() {
ApplicationDependencies.getDatabaseObserver().notifyStickerPackObservers();
}
@Deprecated
protected void setNotifyConversationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
}
@Deprecated
protected void setNotifyConversationListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForAllThreads());
}
@Deprecated
protected void setNotifyVerboseConversationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId));
}
@Deprecated
protected void setNotifyStickerListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Sticker.CONTENT_URI);
}
@Deprecated
protected void setNotifyStickerPackListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.StickerPack.CONTENT_URI);
}
protected void registerAttachmentListeners(@NonNull ContentObserver observer) {
context.getContentResolver().registerContentObserver(DatabaseContentProviders.Attachment.CONTENT_URI,
true,
observer);
protected void notifyStickerListeners() {
ApplicationDependencies.getDatabaseObserver().notifyStickerObservers();
}
protected void notifyAttachmentListeners() {
context.getContentResolver().notifyChange(DatabaseContentProviders.Attachment.CONTENT_URI, null);
ApplicationDependencies.getDatabaseObserver().notifyAttachmentObservers();
}
public void reset(SQLCipherOpenHelper databaseHelper) {

View file

@ -1,92 +0,0 @@
package org.thoughtcrime.securesms.database;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.BuildConfig;
/**
* Starting in API 26, a {@link ContentProvider} needs to be defined for each authority you wish to
* observe changes on. These classes essentially do nothing except exist so Android doesn't complain.
*/
public class DatabaseContentProviders {
public static class Conversation extends NoopContentProvider {
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".database.conversation";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY + "/";
public static Uri getUriForThread(long threadId) {
return Uri.parse(CONTENT_URI_STRING + threadId);
}
public static Uri getVerboseUriForThread(long threadId) {
return Uri.parse(CONTENT_URI_STRING + "verbose/" + threadId);
}
public static Uri getUriForAllThreads() {
return Uri.parse(CONTENT_URI_STRING);
}
}
public static class Attachment extends NoopContentProvider {
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".database.attachment";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
}
public static class Sticker extends NoopContentProvider {
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".database.sticker";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
}
public static class StickerPack extends NoopContentProvider {
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".database.stickerpack";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
}
private static abstract class NoopContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
}

View file

@ -31,7 +31,9 @@ public final class DatabaseObserver {
private final Map<UUID, Set<Observer>> paymentObservers;
private final Set<Observer> allPaymentsObservers;
private final Set<Observer> chatColorsObservers;
private final Set<Observer> stickerObservers;
private final Set<Observer> stickerPackObservers;
private final Set<Observer> attachmentObservers;
private final Set<MessageObserver> messageUpdateObservers;
private final Map<Long, Set<MessageObserver>> messageInsertObservers;
@ -44,7 +46,9 @@ public final class DatabaseObserver {
this.paymentObservers = new HashMap<>();
this.allPaymentsObservers = new HashSet<>();
this.chatColorsObservers = new HashSet<>();
this.stickerObservers = new HashSet<>();
this.stickerPackObservers = new HashSet<>();
this.attachmentObservers = new HashSet<>();
this.messageUpdateObservers = new HashSet<>();
this.messageInsertObservers = new HashMap<>();
}
@ -85,12 +89,24 @@ public final class DatabaseObserver {
});
}
public void registerStickerObserver(@NonNull Observer listener) {
executor.execute(() -> {
stickerObservers.add(listener);
});
}
public void registerStickerPackObserver(@NonNull Observer listener) {
executor.execute(() -> {
stickerPackObservers.add(listener);
});
}
public void registerAttachmentObserver(@NonNull Observer listener) {
executor.execute(() -> {
attachmentObservers.add(listener);
});
}
public void registerMessageUpdateObserver(@NonNull MessageObserver listener) {
executor.execute(() -> {
messageUpdateObservers.add(listener);
@ -110,7 +126,9 @@ public final class DatabaseObserver {
unregisterMapped(verboseConversationObservers, listener);
unregisterMapped(paymentObservers, listener);
chatColorsObservers.remove(listener);
stickerObservers.remove(listener);
stickerPackObservers.remove(listener);
attachmentObservers.remove(listener);
});
}
@ -127,11 +145,6 @@ public final class DatabaseObserver {
notifyMapped(conversationObservers, threadId);
notifyMapped(verboseConversationObservers, threadId);
}
for (long threadId : threadIds) {
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getUriForThread(threadId), null);
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId), null);
}
});
}
@ -139,9 +152,6 @@ public final class DatabaseObserver {
executor.execute(() -> {
notifyMapped(conversationObservers, threadId);
notifyMapped(verboseConversationObservers, threadId);
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getUriForThread(threadId), null);
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId), null);
});
}
@ -150,18 +160,12 @@ public final class DatabaseObserver {
for (long threadId : threadIds) {
notifyMapped(verboseConversationObservers, threadId);
}
for (long threadId : threadIds) {
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId), null);
}
});
}
public void notifyVerboseConversationListeners(long threadId) {
executor.execute(() -> {
notifyMapped(verboseConversationObservers, threadId);
application.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId), null);
});
}
@ -193,11 +197,21 @@ public final class DatabaseObserver {
});
}
public void notifyStickerObservers() {
executor.execute(() -> {
notifySet(stickerObservers);
});
}
public void notifyStickerPackObservers() {
executor.execute(() -> {
notifySet(stickerPackObservers);
});
}
application.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null);
public void notifyAttachmentObservers() {
executor.execute(() -> {
notifySet(attachmentObservers);
});
}

View file

@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.MediaUtil;
@ -96,54 +97,38 @@ public class MediaDatabase extends Database {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY));
String[] args = {threadId + ""};
Cursor cursor = database.rawQuery(query, args);
if (listenToAllThreads) {
setNotifyConversationListeners(cursor);
} else {
setNotifyConversationListeners(cursor, threadId);
}
return cursor;
return database.rawQuery(query, args);
}
public @NonNull Cursor getDocumentMediaForThread(long threadId, @NonNull Sorting sorting) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String query = sorting.applyToQuery(applyEqualityOperator(threadId, DOCUMENT_MEDIA_QUERY));
String[] args = {threadId + ""};
Cursor cursor = database.rawQuery(query, args);
setNotifyConversationListeners(cursor, threadId);
return cursor;
return database.rawQuery(query, args);
}
public @NonNull Cursor getAudioMediaForThread(long threadId, @NonNull Sorting sorting) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String query = sorting.applyToQuery(applyEqualityOperator(threadId, AUDIO_MEDIA_QUERY));
String[] args = {threadId + ""};
Cursor cursor = database.rawQuery(query, args);
setNotifyConversationListeners(cursor, threadId);
return cursor;
return database.rawQuery(query, args);
}
public @NonNull Cursor getAllMediaForThread(long threadId, @NonNull Sorting sorting) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String query = sorting.applyToQuery(applyEqualityOperator(threadId, ALL_MEDIA_QUERY));
String[] args = {threadId + ""};
Cursor cursor = database.rawQuery(query, args);
setNotifyConversationListeners(cursor, threadId);
return cursor;
return database.rawQuery(query, args);
}
private static String applyEqualityOperator(long threadId, String query) {
return query.replace("__EQUALITY__", threadId == ALL_THREADS ? "!=" : "=");
}
public void subscribeToMediaChanges(@NonNull ContentObserver observer) {
registerAttachmentListeners(observer);
}
public void unsubscribeToMediaChanges(@NonNull ContentObserver observer) {
context.getContentResolver().unregisterContentObserver(observer);
}
public StorageBreakdown getStorageBreakdown() {
StorageBreakdown storageBreakdown = new StorageBreakdown();
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();

View file

@ -90,7 +90,6 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract OutgoingMediaMessage getOutgoingMessage(long messageId) throws MmsException, NoSuchMessageException;
public abstract MessageRecord getMessageRecord(long messageId) throws NoSuchMessageException;
public abstract @Nullable MessageRecord getMessageRecordOrNull(long messageId);
public abstract Cursor getVerboseMessageCursor(long messageId);
public abstract boolean hasReceivedAnyCallsSince(long threadId, long timestamp);
public abstract @Nullable ViewOnceExpirationInfo getNearestExpiringViewOnceMessage();
public abstract boolean isSent(long messageId);

View file

@ -275,16 +275,7 @@ public class MmsDatabase extends MessageDatabase {
@Override
public Cursor getMessageCursor(long messageId) {
Cursor cursor = internalGetMessage(messageId);
setNotifyConversationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
}
@Override
public Cursor getVerboseMessageCursor(long messageId) {
Cursor cursor = internalGetMessage(messageId);
setNotifyVerboseConversationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
return internalGetMessage(messageId);
}
@Override
@ -843,6 +834,8 @@ public class MmsDatabase extends MessageDatabase {
long threadId;
boolean deletedAttachments = false;
db.beginTransaction();
try {
ContentValues values = new ContentValues();
@ -856,7 +849,7 @@ public class MmsDatabase extends MessageDatabase {
values.putNull(SHARED_CONTACTS);
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(messageId) });
DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentsForMessage(messageId);
deletedAttachments = DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentsForMessage(messageId);
DatabaseFactory.getMentionDatabase(context).deleteMentionsForMessage(messageId);
DatabaseFactory.getMessageLogDatabase(context).deleteAllRelatedToMessage(messageId, true);
@ -866,8 +859,13 @@ public class MmsDatabase extends MessageDatabase {
} finally {
db.endTransaction();
}
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId, true));
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
if (deletedAttachments) {
ApplicationDependencies.getDatabaseObserver().notifyAttachmentObservers();
}
}
@Override

View file

@ -200,26 +200,13 @@ public class MmsSmsDatabase extends Database {
String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null;
String query = buildQuery(PROJECTION, selection, order, limitStr, false);
Cursor cursor = db.rawQuery(query, null);
setNotifyConversationListeners(cursor, threadId);
return cursor;
return db.rawQuery(query, null);
}
public Cursor getConversation(long threadId) {
return getConversation(threadId, 0, 0);
}
public Cursor getIdentityConflictMessagesForThread(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.MISMATCHED_IDENTITIES + " IS NOT NULL";
Cursor cursor = queryTables(PROJECTION, selection, order, null);
setNotifyConversationListeners(cursor, threadId);
return cursor;
}
public @NonNull MessageRecord getConversationSnippet(long threadId) throws NoSuchMessageException {
try (Cursor cursor = getConversationSnippetCursor(threadId)) {
if (cursor.moveToFirst()) {

View file

@ -1320,13 +1320,6 @@ public class SmsDatabase extends MessageDatabase {
else return record;
}
@Override
public Cursor getVerboseMessageCursor(long messageId) {
Cursor cursor = getMessageCursor(messageId);
setNotifyVerboseConversationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
}
@Override
public Cursor getMessageCursor(long messageId) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();

View file

@ -145,20 +145,15 @@ public class StickerDatabase extends Database {
public @Nullable Cursor getInstalledStickerPacks() {
String selection = COVER + " = ? AND " + INSTALLED + " = ?";
String[] args = new String[] { "1", "1" };
Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, PACK_ORDER + " ASC");
setNotifyStickerPackListeners(cursor);
return cursor;
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, PACK_ORDER + " ASC");
}
public @Nullable Cursor getStickersByEmoji(@NonNull String emoji) {
String selection = EMOJI + " LIKE ? AND " + COVER + " = ?";
String[] args = new String[] { "%"+emoji+"%", "0" };
Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
setNotifyStickerListeners(cursor);
return cursor;
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
}
public @Nullable Cursor getAllStickerPacks() {
@ -168,10 +163,8 @@ public class StickerDatabase extends Database {
public @Nullable Cursor getAllStickerPacks(@Nullable String limit) {
String query = COVER + " = ?";
String[] args = new String[] { "1" };
Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, PACK_ORDER + " ASC", limit);
setNotifyStickerPackListeners(cursor);
return cursor;
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, PACK_ORDER + " ASC", limit);
}
public @Nullable Cursor getStickersForPack(@NonNull String packId) {
@ -179,10 +172,7 @@ public class StickerDatabase extends Database {
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
String[] args = new String[] { packId, "0" };
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null);
setNotifyStickerListeners(cursor);
return cursor;
return db.query(TABLE_NAME, null, selection, args, null, null, null);
}
public @Nullable Cursor getRecentlyUsedStickers(int limit) {
@ -190,10 +180,7 @@ public class StickerDatabase extends Database {
String selection = LAST_USED + " > ? AND " + COVER + " = ?";
String[] args = new String[] { "0", "0" };
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit));
setNotifyStickerListeners(cursor);
return cursor;
return db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit));
}
public @NonNull Set<String> getAllStickerFiles() {

View file

@ -291,7 +291,6 @@ public class ThreadDatabase extends Database {
}
notifyAttachmentListeners();
notifyStickerListeners();
notifyStickerPackListeners();
}
@ -326,7 +325,6 @@ public class ThreadDatabase extends Database {
}
notifyAttachmentListeners();
notifyStickerListeners();
notifyStickerPackListeners();
}

View file

@ -8,10 +8,13 @@ import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.loader.content.AsyncTaskLoader;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.CalendarDateOnly;
import java.text.SimpleDateFormat;
@ -25,10 +28,10 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader<GroupedThrea
@SuppressWarnings("unused")
private static final String TAG = Log.tag(GroupedThreadMediaLoader.class);
private final ContentObserver observer;
private final MediaLoader.MediaType mediaType;
private final MediaDatabase.Sorting sorting;
private final long threadId;
private final DatabaseObserver.Observer observer;
private final MediaLoader.MediaType mediaType;
private final MediaDatabase.Sorting sorting;
private final long threadId;
public GroupedThreadMediaLoader(@NonNull Context context,
long threadId,
@ -39,7 +42,7 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader<GroupedThrea
this.threadId = threadId;
this.mediaType = mediaType;
this.sorting = sorting;
this.observer = new ForceLoadContentObserver();
this.observer = () -> ThreadUtil.runOnMain(this::onContentChanged);
onContentChanged();
}
@ -58,7 +61,7 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader<GroupedThrea
@Override
protected void onAbandon() {
DatabaseFactory.getMediaDatabase(getContext()).unsubscribeToMediaChanges(observer);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
}
@Override
@ -70,7 +73,8 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader<GroupedThrea
PopulatedGroupedThreadMedia mediaGrouping = new PopulatedGroupedThreadMedia(groupingMethod);
DatabaseFactory.getMediaDatabase(context).subscribeToMediaChanges(observer);
ApplicationDependencies.getDatabaseObserver().registerAttachmentObserver(observer);
try (Cursor cursor = ThreadMediaLoader.createThreadMediaCursor(context, threadId, mediaType, sorting)) {
while (cursor != null && cursor.moveToNext()) {
mediaGrouping.add(MediaDatabase.MediaRecord.from(context, cursor));

View file

@ -1,47 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
public class MessageDetailsLoader extends AbstractCursorLoader {
private final String type;
private final long messageId;
public MessageDetailsLoader(Context context, String type, long messageId) {
super(context);
this.type = type;
this.messageId = messageId;
}
@Override
public Cursor getCursor() {
switch (type) {
case MmsSmsDatabase.SMS_TRANSPORT:
return DatabaseFactory.getSmsDatabase(context).getVerboseMessageCursor(messageId);
case MmsSmsDatabase.MMS_TRANSPORT:
return DatabaseFactory.getMmsDatabase(context).getVerboseMessageCursor(messageId);
default:
throw new AssertionError("no valid message type specified");
}
}
}

View file

@ -8,12 +8,15 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.Sorting;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.AsyncLoader;
@ -22,10 +25,11 @@ public final class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>>
@SuppressWarnings("unused")
private static final String TAG = Log.tag(PagingMediaLoader.class);
private final Uri uri;
private final boolean leftIsRecent;
private final Sorting sorting;
private final long threadId;
private final Uri uri;
private final boolean leftIsRecent;
private final Sorting sorting;
private final long threadId;
private final DatabaseObserver.Observer observer;
public PagingMediaLoader(@NonNull Context context, long threadId, @NonNull Uri uri, boolean leftIsRecent, @NonNull Sorting sorting) {
super(context);
@ -33,10 +37,15 @@ public final class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>>
this.uri = uri;
this.leftIsRecent = leftIsRecent;
this.sorting = sorting;
this.observer = () -> {
ThreadUtil.runOnMain(this::onContentChanged);
};
}
@Override
public @Nullable Pair<Cursor, Integer> loadInBackground() {
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, observer);
Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId, sorting, threadId == MediaDatabase.ALL_THREADS);
while (cursor.moveToNext()) {
@ -50,4 +59,9 @@ public final class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>>
return null;
}
@Override
protected void onAbandon() {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
}
}

View file

@ -94,6 +94,7 @@ class StickerKeyboardPageFragment :
view.findViewById<View>(R.id.sticker_search).setOnClickListener { StickerSearchDialogFragment.show(requireActivity().supportFragmentManager) }
view.findViewById<View>(R.id.sticker_manage).setOnClickListener { findListener<StickerEventListener>()?.onStickerManagementClicked() }
ApplicationDependencies.getDatabaseObserver().registerStickerObserver(this)
ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(this)
view.addOnLayoutChangeListener(this)

View file

@ -1,10 +1,6 @@
package org.thoughtcrime.securesms.longmessage;
import android.app.Application;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
@ -12,31 +8,22 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.whispersystems.libsignal.util.guava.Optional;
class LongMessageViewModel extends ViewModel {
private final Application application;
private final LongMessageRepository repository;
private final long messageId;
private final boolean isMms;
private final MutableLiveData<Optional<LongMessage>> message;
private final MessageObserver messageObserver;
private final DatabaseObserver.Observer threadObserver;
private LongMessageViewModel(@NonNull Application application, @NonNull LongMessageRepository repository, long messageId, boolean isMms) {
this.application = application;
this.repository = repository;
this.messageId = messageId;
this.isMms = isMms;
this.message = new MutableLiveData<>();
this.messageObserver = new MessageObserver(new Handler(Looper.getMainLooper()));
this.message = new MutableLiveData<>();
this.threadObserver = () -> repository.getMessage(application, messageId, isMms, message::postValue);
repository.getMessage(application, messageId, isMms, longMessage -> {
if (longMessage.isPresent()) {
Uri uri = DatabaseContentProviders.Conversation.getUriForThread(longMessage.get().getMessageRecord().getThreadId());
application.getContentResolver().registerContentObserver(uri, true, messageObserver);
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(longMessage.get().getMessageRecord().getThreadId(), threadObserver);
}
message.postValue(longMessage);
@ -49,18 +36,7 @@ class LongMessageViewModel extends ViewModel {
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(messageObserver);
}
private class MessageObserver extends ContentObserver {
MessageObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
repository.getMessage(application, messageId, isMms, message::postValue);
}
ApplicationDependencies.getDatabaseObserver().unregisterObserver(threadObserver);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
@ -79,6 +55,7 @@ class LongMessageViewModel extends ViewModel {
@Override
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection ConstantConditions
return modelClass.cast(new LongMessageViewModel(context, repository, messageId, isMms));
}
}

View file

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.messagedetails;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import androidx.annotation.Nullable;
@ -10,18 +9,20 @@ import androidx.lifecycle.LiveData;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
final class MessageRecordLiveData extends LiveData<MessageRecord> {
private final Context context;
private final String type;
private final Long messageId;
private final ContentObserver obs;
private final Context context;
private final String type;
private final Long messageId;
private final DatabaseObserver.Observer observer;
private @Nullable Cursor cursor;
@ -29,13 +30,7 @@ final class MessageRecordLiveData extends LiveData<MessageRecord> {
this.context = context;
this.type = type;
this.messageId = messageId;
obs = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
SignalExecutors.BOUNDED.execute(() -> resetCursor());
}
};
this.observer = () -> SignalExecutors.BOUNDED.execute(this::resetCursor);
}
@Override
@ -54,8 +49,9 @@ final class MessageRecordLiveData extends LiveData<MessageRecord> {
@WorkerThread
private synchronized void destroyCursor() {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
if (cursor != null) {
cursor.unregisterContentObserver(obs);
cursor.close();
cursor = null;
}
@ -87,22 +83,22 @@ final class MessageRecordLiveData extends LiveData<MessageRecord> {
@WorkerThread
private synchronized void handleSms() {
final MessageDatabase db = DatabaseFactory.getSmsDatabase(context);
final Cursor cursor = db.getVerboseMessageCursor(messageId);
final Cursor cursor = db.getMessageCursor(messageId);
final MessageRecord record = SmsDatabase.readerFor(cursor).getNext();
postValue(record);
cursor.registerContentObserver(obs);
ApplicationDependencies.getDatabaseObserver().registerVerboseConversationObserver(record.getThreadId(), observer);
this.cursor = cursor;
}
@WorkerThread
private synchronized void handleMms() {
final MessageDatabase db = DatabaseFactory.getMmsDatabase(context);
final Cursor cursor = db.getVerboseMessageCursor(messageId);
final Cursor cursor = db.getMessageCursor(messageId);
final MessageRecord record = MmsDatabase.readerFor(cursor).getNext();
postValue(record);
cursor.registerContentObserver(obs);
ApplicationDependencies.getDatabaseObserver().registerVerboseConversationObserver(record.getThreadId(), observer);
this.cursor = cursor;
}
}

View file

@ -99,7 +99,7 @@ public class ViewOnceMessageActivity extends PassphraseRequiredActivity implemen
private void initViewModel(long messageId, @NonNull Uri uri) {
ViewOnceMessageRepository repository = new ViewOnceMessageRepository(this);
viewModel = ViewModelProviders.of(this, new ViewOnceMessageViewModel.Factory(getApplication(), messageId, repository))
viewModel = ViewModelProviders.of(this, new ViewOnceMessageViewModel.Factory(messageId, repository))
.get(ViewOnceMessageViewModel.class);
viewModel.getMessage().observe(this, (message) -> {

View file

@ -9,6 +9,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@ -31,8 +32,8 @@ class ViewOnceMessageRepository {
void getMessage(long messageId, @NonNull Callback<Optional<MmsMessageRecord>> callback) {
SignalExecutors.BOUNDED.execute(() -> {
try (MmsDatabase.Reader reader = MmsDatabase.readerFor(mmsDatabase.getMessageCursor(messageId))) {
MmsMessageRecord record = (MmsMessageRecord) reader.getNext();
try {
MmsMessageRecord record = (MmsMessageRecord) mmsDatabase.getMessageRecord(messageId);
MessageDatabase.MarkedMessageInfo info = mmsDatabase.setIncomingMessageViewed(record.getId());
if (info != null) {
@ -44,6 +45,8 @@ class ViewOnceMessageRepository {
}
callback.onComplete(Optional.fromNullable(record));
} catch (NoSuchMessageException e) {
callback.onComplete(Optional.absent());
}
});
}

View file

@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.revealable;
import android.app.Application;
import android.database.ContentObserver;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
@ -12,37 +10,25 @@ import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.whispersystems.libsignal.util.guava.Optional;
class ViewOnceMessageViewModel extends ViewModel {
private static final String TAG = Log.tag(ViewOnceMessageViewModel.class);
private final Application application;
private final ViewOnceMessageRepository repository;
private final MutableLiveData<Optional<MmsMessageRecord>> message;
private final ContentObserver observer;
private final DatabaseObserver.Observer observer;
private ViewOnceMessageViewModel(@NonNull Application application,
long messageId,
@NonNull ViewOnceMessageRepository repository)
{
this.application = application;
this.repository = repository;
this.message = new MutableLiveData<>();
this.observer = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
repository.getMessage(messageId, optionalMessage -> onMessageRetrieved(optionalMessage));
}
};
private ViewOnceMessageViewModel(long messageId, @NonNull ViewOnceMessageRepository repository) {
this.message = new MutableLiveData<>();
this.observer = () -> repository.getMessage(messageId, this::onMessageRetrieved);
repository.getMessage(messageId, message -> {
if (message.isPresent()) {
Uri uri = DatabaseContentProviders.Conversation.getUriForThread(message.get().getThreadId());
application.getContentResolver().registerContentObserver(uri, true, observer);
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(message.get().getThreadId(), observer);
}
onMessageRetrieved(message);
@ -55,7 +41,7 @@ class ViewOnceMessageViewModel extends ViewModel {
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(observer);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
}
private void onMessageRetrieved(@NonNull Optional<MmsMessageRecord> optionalMessage) {
@ -73,15 +59,10 @@ class ViewOnceMessageViewModel extends ViewModel {
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final Application application;
private final long messageId;
private final ViewOnceMessageRepository repository;
Factory(@NonNull Application application,
long messageId,
@NonNull ViewOnceMessageRepository repository)
{
this.application = application;
Factory(long messageId, @NonNull ViewOnceMessageRepository repository) {
this.messageId = messageId;
this.repository = repository;
}
@ -89,7 +70,7 @@ class ViewOnceMessageViewModel extends ViewModel {
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection ConstantConditions
return modelClass.cast(new ViewOnceMessageViewModel(application, messageId, repository));
return modelClass.cast(new ViewOnceMessageViewModel(messageId, repository));
}
}
}

View file

@ -14,7 +14,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactRepository;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MentionDatabase;
@ -118,7 +117,7 @@ public class SearchRepository {
public void query(@NonNull String query, long threadId, @NonNull Callback<List<MessageResult>> callback) {
if (TextUtils.isEmpty(query)) {
callback.onResult(CursorList.emptyList());
callback.onResult(Collections.emptyList());
return;
}
@ -345,11 +344,11 @@ public class SearchRepository {
return body;
}
private @NonNull <T> List<T> readToList(@Nullable Cursor cursor, @NonNull CursorList.ModelBuilder<T> builder) {
private @NonNull <T> List<T> readToList(@Nullable Cursor cursor, @NonNull ModelBuilder<T> builder) {
return readToList(cursor, builder, -1);
}
private @NonNull <T> List<T> readToList(@Nullable Cursor cursor, @NonNull CursorList.ModelBuilder<T> builder, int limit) {
private @NonNull <T> List<T> readToList(@Nullable Cursor cursor, @NonNull ModelBuilder<T> builder, int limit) {
if (cursor == null) {
return Collections.emptyList();
}
@ -396,7 +395,7 @@ public class SearchRepository {
return combined;
}
private static class RecipientModelBuilder implements CursorList.ModelBuilder<Recipient> {
private static class RecipientModelBuilder implements ModelBuilder<Recipient> {
@Override
public Recipient build(@NonNull Cursor cursor) {
@ -405,7 +404,7 @@ public class SearchRepository {
}
}
private static class ThreadModelBuilder implements CursorList.ModelBuilder<ThreadRecord> {
private static class ThreadModelBuilder implements ModelBuilder<ThreadRecord> {
private final ThreadDatabase threadDatabase;
@ -419,7 +418,7 @@ public class SearchRepository {
}
}
private static class MessageModelBuilder implements CursorList.ModelBuilder<MessageResult> {
private static class MessageModelBuilder implements ModelBuilder<MessageResult> {
@Override
public MessageResult build(@NonNull Cursor cursor) {
@ -441,4 +440,8 @@ public class SearchRepository {
public interface Callback<E> {
void onResult(@NonNull E result);
}
public interface ModelBuilder<T> {
T build(@NonNull Cursor cursor);
}
}

View file

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.stickers;
import android.app.Application;
import android.database.ContentObserver;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
@ -9,8 +8,9 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.stickers.StickerManagementRepository.PackResult;
import java.util.List;
@ -20,21 +20,18 @@ final class StickerManagementViewModel extends ViewModel {
private final Application application;
private final StickerManagementRepository repository;
private final MutableLiveData<PackResult> packs;
private final ContentObserver observer;
private final DatabaseObserver.Observer observer;
private StickerManagementViewModel(@NonNull Application application, @NonNull StickerManagementRepository repository) {
this.application = application;
this.repository = repository;
this.packs = new MutableLiveData<>();
this.observer = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
repository.deleteOrphanedStickerPacks();
repository.getStickerPacks(packs::postValue);
}
this.observer = () -> {
repository.deleteOrphanedStickerPacks();
repository.getStickerPacks(packs::postValue);
};
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, observer);
ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(observer);
}
void init() {
@ -65,7 +62,7 @@ final class StickerManagementViewModel extends ViewModel {
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(observer);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {

View file

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.stickers;
import android.app.Application;
import android.database.ContentObserver;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -10,7 +9,8 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository.StickerManifestResult;
import org.whispersystems.libsignal.util.guava.Optional;
@ -20,7 +20,7 @@ final class StickerPackPreviewViewModel extends ViewModel {
private final StickerPackPreviewRepository previewRepository;
private final StickerManagementRepository managementRepository;
private final MutableLiveData<Optional<StickerManifestResult>> stickerManifest;
private final ContentObserver packObserver;
private final DatabaseObserver.Observer packObserver;
private String packId;
private String packKey;
@ -33,16 +33,13 @@ final class StickerPackPreviewViewModel extends ViewModel {
this.previewRepository = previewRepository;
this.managementRepository = managementRepository;
this.stickerManifest = new MutableLiveData<>();
this.packObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
if (!TextUtils.isEmpty(packId) && !TextUtils.isEmpty(packKey)) {
previewRepository.getStickerManifest(packId, packKey, stickerManifest::postValue);
}
this.packObserver = () -> {
if (!TextUtils.isEmpty(packId) && !TextUtils.isEmpty(packKey)) {
previewRepository.getStickerManifest(packId, packKey, stickerManifest::postValue);
}
};
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(packObserver);
}
LiveData<Optional<StickerManifestResult>> getStickerManifest(@NonNull String packId, @NonNull String packKey) {
@ -64,7 +61,7 @@ final class StickerPackPreviewViewModel extends ViewModel {
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(packObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(packObserver);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {

View file

@ -8,7 +8,6 @@ import androidx.annotation.NonNull;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase.StickerRecordReader;
@ -59,13 +58,6 @@ public final class StickerSearchRepository {
});
}
private static class StickerModelBuilder implements CursorList.ModelBuilder<StickerRecord> {
@Override
public StickerRecord build(@NonNull Cursor cursor) {
return new StickerRecordReader(cursor).getCurrent();
}
}
public interface Callback<T> {
void onResult(T result);
}

View file

@ -1,47 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.database.ContentObserver;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import org.signal.core.util.StreamUtil;
import org.thoughtcrime.securesms.database.ObservableContent;
import java.io.Closeable;
/**
* Implementation of {@link androidx.lifecycle.LiveData} that will handle closing the contained
* {@link Closeable} when the value changes.
*/
public class ObservingLiveData<E extends ObservableContent> extends MutableLiveData<E> {
private ContentObserver observer;
@Override
public void setValue(E value) {
E previous = getValue();
if (previous != null) {
previous.unregisterContentObserver(observer);
StreamUtil.close(previous);
}
value.registerContentObserver(observer);
super.setValue(value);
}
public void close() {
E value = getValue();
if (value != null) {
value.unregisterContentObserver(observer);
StreamUtil.close(value);
}
}
public void registerContentObserver(@NonNull ContentObserver observer) {
this.observer = observer;
}
}