From 3c069fb58814cfb18e9572cd9bc8d859d9d8f3f4 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 9 Jul 2020 12:19:58 -0400 Subject: [PATCH] Enable Media Preview to respond to media changes. --- .../securesms/MediaPreviewActivity.java | 92 ++++++++++++++++--- .../database/AttachmentDatabase.java | 17 ++++ .../securesms/database/Database.java | 4 + .../database/DatabaseContentProviders.java | 4 + .../securesms/database/MediaDatabase.java | 10 +- .../database/loaders/PagingMediaLoader.java | 3 +- .../mediapreview/MediaPreviewFragment.java | 27 +++++- app/src/main/res/values/strings.xml | 1 + 8 files changed, 139 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 40b16a3e70..acd5e510bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -20,10 +20,12 @@ import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; +import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -73,6 +75,7 @@ import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; /** * Activity for displaying media attachments in-app @@ -117,17 +120,20 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity private boolean showThread; private MediaDatabase.Sorting sorting; + private @Nullable Cursor cursor = null; + public static @NonNull Intent intentFromMediaRecord(@NonNull Context context, @NonNull MediaRecord mediaRecord, boolean leftIsRecent) { + DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); Intent intent = new Intent(context, MediaPreviewActivity.class); intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, mediaRecord.getThreadId()); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate()); - intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize()); - intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, mediaRecord.getAttachment().getCaption()); + intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, attachment.getSize()); + intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, attachment.getCaption()); intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent); - intent.setDataAndType(mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType()); + intent.setDataAndType(attachment.getDataUri(), mediaRecord.getContentType()); return intent; } @@ -228,6 +234,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity restartItem = cleanupMedia(); } + @Override + protected void onDestroy() { + if (cursor != null) { + cursor.close(); + cursor = null; + } + super.onDestroy(); + } + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -344,6 +359,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity mediaPager.removeAllViews(); mediaPager.setAdapter(null); + viewModel.setCursor(this, null, leftIsRecent); return restartItem; } @@ -475,19 +491,46 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity @Override public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) { if (data != null) { - @SuppressWarnings("ConstantConditions") - CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, data.first, data.second, leftIsRecent); + if (data.first == cursor) { + return; + } + + if (cursor != null) { + cursor.close(); + } + cursor = Objects.requireNonNull(data.first); + + int mediaPosition = Objects.requireNonNull(data.second); + + CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, cursor, mediaPosition, leftIsRecent); mediaPager.setAdapter(adapter); adapter.setActive(true); - viewModel.setCursor(this, data.first, leftIsRecent); + viewModel.setCursor(this, cursor, leftIsRecent); - int item = restartItem >= 0 ? restartItem : data.second; + int item = restartItem >= 0 ? restartItem : mediaPosition; mediaPager.setCurrentItem(item); 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()); } } @@ -502,6 +545,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity return true; } + @Override + public void mediaNotAvailable() { + Toast.makeText(this, R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show(); + finish(); + } + private void toggleUiVisibility() { int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility(); if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) { @@ -621,6 +670,11 @@ 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) { @@ -712,7 +766,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity cursor.moveToPosition(cursorPosition); MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor); - DatabaseAttachment attachment = mediaRecord.getAttachment(); + DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay); mediaFragments.put(position, fragment); @@ -734,16 +788,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity public MediaItem getMediaItemFor(int position) { cursor.moveToPosition(getCursorPosition(position)); - MediaRecord mediaRecord = MediaRecord.from(context, cursor); - RecipientId recipientId = mediaRecord.getRecipientId(); - RecipientId threadRecipientId = mediaRecord.getThreadRecipientId(); - - if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); + MediaRecord mediaRecord = MediaRecord.from(context, cursor); + DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment()); + RecipientId recipientId = mediaRecord.getRecipientId(); + RecipientId threadRecipientId = mediaRecord.getThreadRecipientId(); return new MediaItem(Recipient.live(recipientId).get(), Recipient.live(threadRecipientId).get(), - mediaRecord.getAttachment(), - mediaRecord.getAttachment().getDataUri(), + attachment, + Objects.requireNonNull(attachment.getDataUri()), mediaRecord.getContentType(), mediaRecord.getDate(), mediaRecord.isOutgoing()); @@ -767,6 +820,14 @@ 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; @@ -805,5 +866,6 @@ 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/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index f20c6cc0c8..e51a4bb8e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -309,6 +309,23 @@ public class AttachmentDatabase extends Database { } } + public boolean hasAttachment(@NonNull AttachmentId id) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + + try (Cursor cursor = database.query(TABLE_NAME, + new String[]{ROW_ID, UNIQUE_ID}, + PART_ID_WHERE, + id.toStrings(), + null, + null, + null)) { + if (cursor != null && cursor.getCount() > 0) { + return true; + } + } + 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) }; 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 b5f04dbaf3..68cf0c761d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -67,6 +67,10 @@ public abstract class Database { cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId)); } + protected void setNotifyConversationListeners(Cursor cursor) { + cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForAllThreads()); + } + protected void setNotifyVerboseConversationListeners(Cursor cursor, long threadId) { cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java index 422a455fe0..da62f5fd29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java @@ -27,6 +27,10 @@ public class DatabaseContentProviders { 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 { 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 f68b1758d6..bde1c063ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -89,11 +89,19 @@ public class MediaDatabase extends Database { } public @NonNull Cursor getGalleryMediaForThread(long threadId, @NonNull Sorting sorting) { + return getGalleryMediaForThread(threadId, sorting, false); + } + + public @NonNull Cursor getGalleryMediaForThread(long threadId, @NonNull Sorting sorting, boolean listenToAllThreads) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); String query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY)); String[] args = {threadId + ""}; Cursor cursor = database.rawQuery(query, args); - setNotifyConversationListeners(cursor, threadId); + if (listenToAllThreads) { + setNotifyConversationListeners(cursor); + } else { + setNotifyConversationListeners(cursor, threadId); + } return cursor; } 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 d01e12bad5..8f7664b23b 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 @@ -11,6 +11,7 @@ import androidx.core.util.Pair; import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.MediaDatabase.Sorting; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.AsyncLoader; @@ -35,7 +36,7 @@ public final class PagingMediaLoader extends AsyncLoader> @Override public @Nullable Pair loadInBackground() { - Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId, sorting); + Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId, sorting, threadId == MediaDatabase.ALL_THREADS); while (cursor.moveToNext()) { AttachmentId attachmentId = new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java index 867bb8cb6f..16e2139983 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java @@ -10,7 +10,13 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.AttachmentId; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.mms.PartUriParser; import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.concurrent.SimpleTask; + +import java.util.Objects; public abstract class MediaPreviewFragment extends Fragment { @@ -19,7 +25,8 @@ public abstract class MediaPreviewFragment extends Fragment { static final String DATA_CONTENT_TYPE = "DATA_CONTENT_TYPE"; static final String AUTO_PLAY = "AUTO_PLAY"; - protected Events events; + private AttachmentId attachmentId; + protected Events events; public static MediaPreviewFragment newInstance(@NonNull Attachment attachment, boolean autoPlay) { return newInstance(attachment.getDataUri(), attachment.getContentType(), attachment.getSize(), autoPlay); @@ -60,6 +67,12 @@ public abstract class MediaPreviewFragment extends Fragment { events = (Events) context; } + @Override + public void onResume() { + super.onResume(); + checkMediaStillAvailable(); + } + public void cleanUp() { } @@ -70,8 +83,18 @@ public abstract class MediaPreviewFragment extends Fragment { return null; } - public interface Events { + public void checkMediaStillAvailable() { + if (attachmentId == null) { + attachmentId = new PartUriParser(Objects.requireNonNull(requireArguments().getParcelable(DATA_URI))).getPartId(); + } + SimpleTask.run(getViewLifecycleOwner().getLifecycle(), + () -> DatabaseFactory.getAttachmentDatabase(requireContext()).hasAttachment(attachmentId), + hasAttachment -> { if (!hasAttachment) events.mediaNotAvailable(); }); + } + + public interface Events { boolean singleTapOnMedia(); + void mediaNotAvailable(); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9be1cae528..20a0d49506 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1271,6 +1271,7 @@ Delete message? This will permanently delete this message. %1$s to %2$s + Media no longer available. %1$d new messages in %2$d conversations