kopia lustrzana https://github.com/ryukoposting/Signal-Android
				
				
				
			Add support for smarter story downloads.
							rodzic
							
								
									c4bc2162f2
								
							
						
					
					
						commit
						17111abc72
					
				|  | @ -195,7 +195,10 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns | |||
|   public abstract @NonNull Cursor getStoryReplies(long parentStoryId); | ||||
|   public abstract @Nullable Long getOldestStorySendTimestamp(); | ||||
|   public abstract int deleteStoriesOlderThan(long timestamp); | ||||
|   public abstract @NonNull MessageDatabase.Reader getUnreadStories(@NonNull RecipientId recipientId, int limit); | ||||
| 
 | ||||
|   public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId); | ||||
|   public abstract void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds); | ||||
| 
 | ||||
|   final @NonNull String getOutgoingTypeClause() { | ||||
|     List<String> segments = new ArrayList<>(Types.OUTGOING_MESSAGE_TYPES.length); | ||||
|  |  | |||
|  | @ -590,6 +590,17 @@ public class MmsDatabase extends MessageDatabase { | |||
|     return new Reader(cursor); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public @NonNull MessageDatabase.Reader getUnreadStories(@NonNull RecipientId recipientId, int limit) { | ||||
|     final String query   = IS_STORY_CLAUSE + | ||||
|                            " AND NOT (" + getOutgoingTypeClause() + ") " + | ||||
|                            " AND " + RECIPIENT_ID + " = ?" + | ||||
|                            " AND " + VIEWED_RECEIPT_COUNT + " = ?"; | ||||
|     final String[] args  = SqlUtil.buildArgs(recipientId, 0); | ||||
| 
 | ||||
|     return new Reader(rawQuery(query, args, false, limit)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) { | ||||
|     if (!Stories.isFeatureEnabled()) { | ||||
|  | @ -601,6 +612,28 @@ public class MmsDatabase extends MessageDatabase { | |||
|     return getStoryViewState(threadId); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Synchronizes whether we've viewed a recipient's story based on incoming sync messages. | ||||
|    */ | ||||
|   public void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds) { | ||||
|     final String   timestamps = Util.join(syncMessageIds.stream().map(SyncMessageId::getTimetamp).collect(java.util.stream.Collectors.toList()), ","); | ||||
|     final String[] projection = SqlUtil.buildArgs(RECIPIENT_ID); | ||||
|     final String   where      = IS_STORY_CLAUSE + " AND " + NORMALIZED_DATE_SENT + " IN (" + timestamps + ") AND NOT (" + getOutgoingTypeClause() + ") AND " + VIEWED_RECEIPT_COUNT + " > 0"; | ||||
| 
 | ||||
|     try { | ||||
|       getWritableDatabase().beginTransaction(); | ||||
|       try (Cursor cursor = getWritableDatabase().query(TABLE_NAME, projection, where, null, null, null, null)) { | ||||
|         while (cursor != null && cursor.moveToNext()) { | ||||
|           Recipient recipient = Recipient.resolved(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID))); | ||||
|           SignalDatabase.recipients().updateLastStoryViewTimestamp(recipient.getId()); | ||||
|         } | ||||
|       } | ||||
|       getWritableDatabase().setTransactionSuccessful(); | ||||
|     } finally { | ||||
|       getWritableDatabase().endTransaction(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|   @NonNull StoryViewState getStoryViewState(long threadId) { | ||||
|     final String   hasStoryQuery = "SELECT EXISTS(SELECT 1 FROM " + TABLE_NAME + " WHERE " + IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE + " LIMIT 1)"; | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import org.signal.libsignal.protocol.util.Pair; | |||
| import org.thoughtcrime.securesms.database.MessageDatabase.MessageUpdate; | ||||
| import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId; | ||||
| import org.thoughtcrime.securesms.database.model.MessageRecord; | ||||
| import org.thoughtcrime.securesms.database.model.StoryViewState; | ||||
| import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; | ||||
| import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2; | ||||
| import org.thoughtcrime.securesms.recipients.Recipient; | ||||
|  | @ -529,6 +530,9 @@ public class MmsSmsDatabase extends Database { | |||
|     return SignalDatabase.mms().incrementReceiptCount(syncMessageId, timestamp, receiptType, true); | ||||
|   } | ||||
| 
 | ||||
|   public void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds) { | ||||
|     SignalDatabase.mms().updateViewedStories(syncMessageIds); | ||||
|   } | ||||
| 
 | ||||
|   public void setTimestampRead(@NonNull Recipient senderRecipient, @NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) { | ||||
|     SQLiteDatabase db = getWritableDatabase(); | ||||
|  |  | |||
|  | @ -1964,6 +1964,10 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) : | |||
|     StorageSyncHelper.scheduleSyncForDataChange() | ||||
|   } | ||||
| 
 | ||||
|   fun updateLastStoryViewTimestamp(id: RecipientId) { | ||||
|     updateExtras(id) { it.setLastStoryView(System.currentTimeMillis()) } | ||||
|   } | ||||
| 
 | ||||
|   fun clearUsernameIfExists(username: String) { | ||||
|     val existingUsername = getByUsername(username) | ||||
|     if (existingUsername.isPresent) { | ||||
|  |  | |||
|  | @ -1451,11 +1451,21 @@ public class SmsDatabase extends MessageDatabase { | |||
|     throw new UnsupportedOperationException(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds) { | ||||
|     throw new UnsupportedOperationException(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int deleteStoriesOlderThan(long timestamp) { | ||||
|     throw new UnsupportedOperationException(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public @NonNull MessageDatabase.Reader getUnreadStories(@NonNull RecipientId recipientId, int limit) { | ||||
|     throw new UnsupportedOperationException(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public MessageRecord getMessageRecord(long messageId) throws NoSuchMessageException { | ||||
|     return getSmsMessage(messageId); | ||||
|  |  | |||
|  | @ -177,12 +177,14 @@ import java.util.Arrays; | |||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
| import java.util.UUID; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
|  | @ -1407,13 +1409,7 @@ public final class MessageContentProcessor { | |||
|     } | ||||
| 
 | ||||
|     if (insertResult.isPresent()) { | ||||
|       List<DatabaseAttachment> allAttachments = SignalDatabase.attachments().getAttachmentsForMessage(insertResult.get().getMessageId()); | ||||
|       List<DatabaseAttachment> attachments    = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList(); | ||||
| 
 | ||||
|       for (DatabaseAttachment attachment : attachments) { | ||||
|         ApplicationDependencies.getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); | ||||
|       } | ||||
| 
 | ||||
|       Stories.enqueueNextStoriesForDownload(threadRecipient.getId(), false); | ||||
|       ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary(); | ||||
|     } | ||||
|   } | ||||
|  | @ -2164,6 +2160,11 @@ public final class MessageContentProcessor { | |||
|     Collection<SyncMessageId> unhandled = shouldOnlyProcessStories ? SignalDatabase.mmsSms().incrementViewedStoryReceiptCounts(ids, content.getTimestamp()) | ||||
|                                                                    : SignalDatabase.mmsSms().incrementViewedReceiptCounts(ids, content.getTimestamp()); | ||||
| 
 | ||||
|     Set<SyncMessageId> handled = new HashSet<>(ids); | ||||
|     handled.removeAll(unhandled); | ||||
| 
 | ||||
|     SignalDatabase.mmsSms().updateViewedStories(handled); | ||||
| 
 | ||||
|     for (SyncMessageId id : unhandled) { | ||||
|       warn(String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + "  author: " + senderRecipient.getId()); | ||||
|       if (!processingEarlyContent) { | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import androidx.annotation.WorkerThread; | |||
| 
 | ||||
| import com.annimon.stream.Stream; | ||||
| 
 | ||||
| import org.signal.core.util.StringUtil; | ||||
| import org.signal.core.util.logging.Log; | ||||
| import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; | ||||
| import org.thoughtcrime.securesms.R; | ||||
|  | @ -44,7 +45,6 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; | |||
| import org.thoughtcrime.securesms.profiles.ProfileName; | ||||
| import org.thoughtcrime.securesms.util.AvatarUtil; | ||||
| import org.thoughtcrime.securesms.util.FeatureFlags; | ||||
| import org.signal.core.util.StringUtil; | ||||
| import org.thoughtcrime.securesms.util.Util; | ||||
| import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; | ||||
| import org.whispersystems.signalservice.api.push.PNI; | ||||
|  | @ -725,6 +725,10 @@ public class Recipient { | |||
|     return extras.map(Extras::hideStory).orElse(false); | ||||
|   } | ||||
| 
 | ||||
|   public boolean hasViewedStory() { | ||||
|     return extras.map(Extras::hasViewedStory).orElse(false); | ||||
|   } | ||||
| 
 | ||||
|   public @NonNull GroupId requireGroupId() { | ||||
|     GroupId resolved = resolving ? resolve().groupId : groupId; | ||||
| 
 | ||||
|  | @ -1218,17 +1222,21 @@ public class Recipient { | |||
|       return recipientExtras.getHideStory(); | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasViewedStory() { | ||||
|       return recipientExtras.getLastStoryView() > 0L; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(Object o) { | ||||
|       if (this == o) return true; | ||||
|       if (o == null || getClass() != o.getClass()) return false; | ||||
|       final Extras that = (Extras) o; | ||||
|       return manuallyShownAvatar() == that.manuallyShownAvatar() && hideStory() == that.hideStory(); | ||||
|       return manuallyShownAvatar() == that.manuallyShownAvatar() && hideStory() == that.hideStory() && hasViewedStory() == that.hasViewedStory(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|       return Objects.hash(manuallyShownAvatar(), hideStory()); | ||||
|       return Objects.hash(manuallyShownAvatar(), hideStory(), hasViewedStory()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,9 +5,12 @@ import androidx.fragment.app.FragmentManager | |||
| import io.reactivex.rxjava3.core.Completable | ||||
| import org.thoughtcrime.securesms.R | ||||
| import org.thoughtcrime.securesms.contacts.HeaderAction | ||||
| import org.thoughtcrime.securesms.database.AttachmentDatabase | ||||
| import org.thoughtcrime.securesms.database.SignalDatabase | ||||
| import org.thoughtcrime.securesms.database.model.DistributionListId | ||||
| import org.thoughtcrime.securesms.database.model.MmsMessageRecord | ||||
| import org.thoughtcrime.securesms.dependencies.ApplicationDependencies | ||||
| import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob | ||||
| import org.thoughtcrime.securesms.keyvalue.SignalStore | ||||
| import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet | ||||
| import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage | ||||
|  | @ -72,4 +75,24 @@ object Stories { | |||
|     SignalDatabase.recipients.markNeedsSync(storyRecipientId) | ||||
|     StorageSyncHelper.scheduleSyncForDataChange() | ||||
|   } | ||||
| 
 | ||||
|   @JvmStatic | ||||
|   @WorkerThread | ||||
|   fun enqueueNextStoriesForDownload(recipientId: RecipientId, ignoreAutoDownloadConstraints: Boolean = false) { | ||||
|     val recipient = Recipient.resolved(recipientId) | ||||
|     if (!recipient.isSelf && (recipient.shouldHideStory() || !recipient.hasViewedStory())) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     val unreadStoriesReader = SignalDatabase.mms.getUnreadStories(recipientId, FeatureFlags.storiesAutoDownloadMaximum()) | ||||
|     while (unreadStoriesReader.next != null) { | ||||
|       val record = unreadStoriesReader.current as MmsMessageRecord | ||||
|       SignalDatabase.attachments.getAttachmentsForMessage(record.id).filterNot { it.isSticker }.forEach { | ||||
|         if (it.transferState == AttachmentDatabase.TRANSFER_PROGRESS_PENDING) { | ||||
|           val job = AttachmentDownloadJob(record.id, it.attachmentId, ignoreAutoDownloadConstraints) | ||||
|           ApplicationDependencies.getJobManager().add(job) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -547,6 +547,10 @@ class StoryViewerPageFragment : | |||
|       AttachmentDatabase.TRANSFER_PROGRESS_DONE -> { | ||||
|         storySlate.moveToState(StorySlateView.State.HIDDEN, post.id) | ||||
|         viewModel.setIsDisplayingSlate(false) | ||||
| 
 | ||||
|         if (post.content.transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { | ||||
|           viewModel.markViewed(post) | ||||
|         } | ||||
|       } | ||||
|       AttachmentDatabase.TRANSFER_PROGRESS_PENDING -> { | ||||
|         storySlate.moveToState(StorySlateView.State.LOADING, post.id) | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob | |||
| import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob | ||||
| import org.thoughtcrime.securesms.recipients.Recipient | ||||
| import org.thoughtcrime.securesms.recipients.RecipientId | ||||
| import org.thoughtcrime.securesms.stories.Stories | ||||
| import org.thoughtcrime.securesms.util.Base64 | ||||
| 
 | ||||
| /** | ||||
|  | @ -173,6 +174,10 @@ open class StoryViewerPageRepository(context: Context) { | |||
|             ) | ||||
|           ) | ||||
|           MultiDeviceViewedUpdateJob.enqueue(listOf(markedMessageInfo.syncMessageId)) | ||||
| 
 | ||||
|           val recipientId = storyPost.group?.id ?: storyPost.sender.id | ||||
|           SignalDatabase.recipients.updateLastStoryViewTimestamp(recipientId) | ||||
|           Stories.enqueueNextStoriesForDownload(recipientId, true) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable | |||
| import io.reactivex.rxjava3.kotlin.plusAssign | ||||
| import io.reactivex.rxjava3.subjects.PublishSubject | ||||
| import io.reactivex.rxjava3.subjects.Subject | ||||
| import org.thoughtcrime.securesms.database.AttachmentDatabase | ||||
| import org.thoughtcrime.securesms.recipients.RecipientId | ||||
| import org.thoughtcrime.securesms.util.livedata.Store | ||||
| import org.thoughtcrime.securesms.util.rx.RxStore | ||||
|  | @ -76,11 +77,15 @@ class StoryViewerPageViewModel( | |||
|     return repository.hideStory(recipientId) | ||||
|   } | ||||
| 
 | ||||
|   fun markViewed(storyPost: StoryPost) { | ||||
|     repository.markViewed(storyPost) | ||||
|   } | ||||
| 
 | ||||
|   fun setSelectedPostIndex(index: Int) { | ||||
|     val selectedPost = getPostAt(index) | ||||
| 
 | ||||
|     if (selectedPost != null) { | ||||
|       repository.markViewed(selectedPost) | ||||
|     if (selectedPost != null && selectedPost.content.transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE) { | ||||
|       repository.forceDownload(selectedPost) | ||||
|     } | ||||
| 
 | ||||
|     store.update { | ||||
|  |  | |||
|  | @ -97,6 +97,7 @@ public final class FeatureFlags { | |||
|   private static final String PAYMENTS_COUNTRY_BLOCKLIST        = "android.payments.blocklist"; | ||||
|   private static final String PNP_CDS                           = "android.pnp.cds"; | ||||
|   private static final String USE_FCM_FOREGROUND_SERVICE        = "android.useFcmForegroundService"; | ||||
|   private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM     = "android.stories.autoDownloadMaximum"; | ||||
| 
 | ||||
|   /** | ||||
|    * We will only store remote values for flags in this set. If you want a flag to be controllable | ||||
|  | @ -145,7 +146,8 @@ public final class FeatureFlags { | |||
|       USE_HARDWARE_AEC_IF_OLD, | ||||
|       USE_AEC3, | ||||
|       PAYMENTS_COUNTRY_BLOCKLIST, | ||||
|       USE_FCM_FOREGROUND_SERVICE | ||||
|       USE_FCM_FOREGROUND_SERVICE, | ||||
|       STORIES_AUTO_DOWNLOAD_MAXIMUM | ||||
|   ); | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|  | @ -512,6 +514,13 @@ public final class FeatureFlags { | |||
|     return getBoolean(USE_FCM_FOREGROUND_SERVICE, false); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Prefetch count for stories from a given user. | ||||
|    */ | ||||
|   public static int storiesAutoDownloadMaximum() { | ||||
|     return getInteger(STORIES_AUTO_DOWNLOAD_MAXIMUM, 2); | ||||
|   } | ||||
| 
 | ||||
|   /** Only for rendering debug info. */ | ||||
|   public static synchronized @NonNull Map<String, Object> getMemoryValues() { | ||||
|     return new TreeMap<>(REMOTE_VALUES); | ||||
|  |  | |||
|  | @ -178,8 +178,9 @@ message ChatColor { | |||
| } | ||||
| 
 | ||||
| message RecipientExtras { | ||||
|     bool manuallyShownAvatar = 1; | ||||
|     bool hideStory           = 2; | ||||
|     bool  manuallyShownAvatar = 1; | ||||
|     bool  hideStory           = 2; | ||||
|     int64 lastStoryView       = 3; | ||||
| } | ||||
| 
 | ||||
| message CustomAvatar { | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Alex Hart
						Alex Hart