diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index afa349d56d..3bf92947d0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -770,22 +770,6 @@ - - - - - - - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 551eda0ccc..6cdd5f61f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -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> 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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java index 6c6a53a4fb..bf609f5c3b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java index ca6bd16b71..d13fd12f5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java @@ -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> stickers; private final MutableLiveData 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> 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index a8bb37fc46..03dbd44676 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -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 getPendingAttachments() { final SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); final List 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CursorList.java b/app/src/main/java/org/thoughtcrime/securesms/database/CursorList.java deleted file mode 100644 index 9c40e19ecd..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CursorList.java +++ /dev/null @@ -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 implements List, ObservableContent { - - private final Cursor cursor; - private final ModelBuilder modelBuilder; - - public CursorList(@NonNull Cursor cursor, @NonNull ModelBuilder modelBuilder) { - this.cursor = cursor; - this.modelBuilder = modelBuilder; - - forceQueryLoad(); - } - - public static CursorList emptyList() { - //noinspection ConstantConditions,unchecked - return (CursorList) 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 iterator() { - return new Iterator() { - 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 listIterator() { - throw new UnsupportedOperationException(); - } - - @Override - public @NonNull ListIterator listIterator(int i) { - throw new UnsupportedOperationException(); - } - - @Override - public @NonNull List 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 build(@NonNull Cursor cursor); - } -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index ef9b971c7d..f0220f4813 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java deleted file mode 100644 index e0d14d585c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java +++ /dev/null @@ -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; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java index 4de69505a3..26d2335492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java @@ -31,7 +31,9 @@ public final class DatabaseObserver { private final Map> paymentObservers; private final Set allPaymentsObservers; private final Set chatColorsObservers; + private final Set stickerObservers; private final Set stickerPackObservers; + private final Set attachmentObservers; private final Set messageUpdateObservers; private final Map> 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); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index 2c67c11f47..41ac2392f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index 1bdef942e0..1ac30b66e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index aabc7b1938..a061a392f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 62f7d80ccd..70fd59fa42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 76bf4842fd..324d915a16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java index cedb115140..2ff7bf382c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java @@ -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 getAllStickerFiles() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 54f14cd6ab..37b592881e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -291,7 +291,6 @@ public class ThreadDatabase extends Database { } notifyAttachmentListeners(); - notifyStickerListeners(); notifyStickerPackListeners(); } @@ -326,7 +325,6 @@ public class ThreadDatabase extends Database { } notifyAttachmentListeners(); - notifyStickerListeners(); notifyStickerPackListeners(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java index 6800d8e9a7..e8ef3a9d21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/GroupedThreadMediaLoader.java @@ -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 ThreadUtil.runOnMain(this::onContentChanged); onContentChanged(); } @@ -58,7 +61,7 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader. - */ -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"); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java index c50fa6d497..fba59c637a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java @@ -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> @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> this.uri = uri; this.leftIsRecent = leftIsRecent; this.sorting = sorting; + this.observer = () -> { + ThreadUtil.runOnMain(this::onContentChanged); + }; } @Override public @Nullable Pair 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> return null; } + + @Override + protected void onAbandon() { + ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/StickerKeyboardPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/StickerKeyboardPageFragment.kt index 2332b218d8..a974638ad7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/StickerKeyboardPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/StickerKeyboardPageFragment.kt @@ -94,6 +94,7 @@ class StickerKeyboardPageFragment : view.findViewById(R.id.sticker_search).setOnClickListener { StickerSearchDialogFragment.show(requireActivity().supportFragmentManager) } view.findViewById(R.id.sticker_manage).setOnClickListener { findListener()?.onStickerManagementClicked() } + ApplicationDependencies.getDatabaseObserver().registerStickerObserver(this) ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(this) view.addOnLayoutChangeListener(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageViewModel.java index 61dba86bde..f9636527b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageViewModel.java @@ -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> 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 create(@NonNull Class modelClass) { + //noinspection ConstantConditions return modelClass.cast(new LongMessageViewModel(context, repository, messageId, isMms)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageRecordLiveData.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageRecordLiveData.java index 791231dd5d..7fa2106549 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageRecordLiveData.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageRecordLiveData.java @@ -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 { - 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 { 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 { @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 { @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; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java index d89c700177..441253cacf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java @@ -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) -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageRepository.java b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageRepository.java index 755fc44aef..5c1ad1b140 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageRepository.java @@ -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> 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()); } }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java index 8247fe9498..78f9b0ce12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java @@ -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> 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 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 create(@NonNull Class modelClass) { //noinspection ConstantConditions - return modelClass.cast(new ViewOnceMessageViewModel(application, messageId, repository)); + return modelClass.cast(new ViewOnceMessageViewModel(messageId, repository)); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java index 9676ed2c47..bc4891233d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java @@ -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> 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 List readToList(@Nullable Cursor cursor, @NonNull CursorList.ModelBuilder builder) { + private @NonNull List readToList(@Nullable Cursor cursor, @NonNull ModelBuilder builder) { return readToList(cursor, builder, -1); } - private @NonNull List readToList(@Nullable Cursor cursor, @NonNull CursorList.ModelBuilder builder, int limit) { + private @NonNull List readToList(@Nullable Cursor cursor, @NonNull ModelBuilder 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 { + private static class RecipientModelBuilder implements ModelBuilder { @Override public Recipient build(@NonNull Cursor cursor) { @@ -405,7 +404,7 @@ public class SearchRepository { } } - private static class ThreadModelBuilder implements CursorList.ModelBuilder { + private static class ThreadModelBuilder implements ModelBuilder { private final ThreadDatabase threadDatabase; @@ -419,7 +418,7 @@ public class SearchRepository { } } - private static class MessageModelBuilder implements CursorList.ModelBuilder { + private static class MessageModelBuilder implements ModelBuilder { @Override public MessageResult build(@NonNull Cursor cursor) { @@ -441,4 +440,8 @@ public class SearchRepository { public interface Callback { void onResult(@NonNull E result); } + + public interface ModelBuilder { + T build(@NonNull Cursor cursor); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.java index d33b3aab98..4bc5564c38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModel.java @@ -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 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewViewModel.java index c8b8041419..e64d8d5bb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewViewModel.java @@ -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> 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> 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerSearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerSearchRepository.java index e48b3b3dac..c8b21501fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerSearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerSearchRepository.java @@ -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 { - @Override - public StickerRecord build(@NonNull Cursor cursor) { - return new StickerRecordReader(cursor).getCurrent(); - } - } - public interface Callback { void onResult(T result); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ObservingLiveData.java b/app/src/main/java/org/thoughtcrime/securesms/util/ObservingLiveData.java deleted file mode 100644 index 7bc2e2eb41..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ObservingLiveData.java +++ /dev/null @@ -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 extends MutableLiveData { - - 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; - } -}