diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java index 990c9b9de0a..6a0b3b8ea86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java @@ -1,9 +1,11 @@ package org.thoughtcrime.securesms.attachments; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.audio.AudioHash; import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties; @@ -51,6 +53,9 @@ public abstract class Attachment { @Nullable private final BlurHash blurHash; + @Nullable + private final AudioHash audioHash; + @NonNull private final TransformProperties transformProperties; @@ -58,7 +63,7 @@ public Attachment(@NonNull String contentType, int transferState, long size, @Nu int cdnNumber, @Nullable String location, @Nullable String key, @Nullable String relay, @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote, int width, int height, boolean quote, long uploadTimestamp, @Nullable String caption, - @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, + @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties) { this.contentType = contentType; @@ -79,6 +84,7 @@ public Attachment(@NonNull String contentType, int transferState, long size, @Nu this.stickerLocator = stickerLocator; this.caption = caption; this.blurHash = blurHash; + this.audioHash = audioHash; this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty(); } @@ -172,6 +178,10 @@ public boolean isSticker() { return blurHash; } + public @Nullable AudioHash getAudioHash() { + return audioHash; + } + public @Nullable String getCaption() { return caption; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java index 651f1619147..5143716b731 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.audio.AudioHash; import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -25,11 +26,11 @@ public DatabaseAttachment(AttachmentId attachmentId, long mmsId, String fileName, int cdnNumber, String location, String key, String relay, byte[] digest, String fastPreflightId, boolean voiceNote, int width, int height, boolean quote, @Nullable String caption, - @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, + @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties, int displayOrder, long uploadTimestamp) { - super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, transformProperties); + super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties); this.attachmentId = attachmentId; this.hasData = hasData; this.hasThumbnail = hasThumbnail; diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java index 28c3055e82c..b73ed8f522e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java @@ -10,7 +10,7 @@ public class MmsNotificationAttachment extends Attachment { public MmsNotificationAttachment(int status, long size) { - super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null); + super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null, null); } @Nullable diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java index e6053ebf3bc..9d1ac04170c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.audio.AudioHash; import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.stickers.StickerLocator; @@ -24,7 +25,7 @@ private PointerAttachment(@NonNull String contentType, int transferState, long s int width, int height, long uploadTimestamp, @Nullable String caption, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash) { - super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null); + super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null); } @Nullable diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java index 4c6f91b079b..bea52ca155c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java @@ -16,7 +16,7 @@ public class TombstoneAttachment extends Attachment { public TombstoneAttachment(@NonNull String contentType, boolean quote) { - super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null); + super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null, null); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java index 391d85363c7..b4ae13fe8d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.audio.AudioHash; import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties; import org.thoughtcrime.securesms.stickers.StickerLocator; @@ -16,18 +17,18 @@ public class UriAttachment extends Attachment { public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size, @Nullable String fileName, boolean voiceNote, boolean quote, @Nullable String caption, - @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties) + @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties) { - this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, quote, caption, stickerLocator, blurHash, transformProperties); + this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, quote, caption, stickerLocator, blurHash, audioHash, transformProperties); } public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri, @NonNull String contentType, int transferState, long size, int width, int height, @Nullable String fileName, @Nullable String fastPreflightId, boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, - @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties) + @Nullable BlurHash blurHash, @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties) { - super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, transformProperties); + super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties); this.dataUri = dataUri; this.thumbnailUri = thumbnailUri; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioHash.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioHash.java new file mode 100644 index 00000000000..6550fe37ff2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioHash.java @@ -0,0 +1,57 @@ +package org.thoughtcrime.securesms.audio; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; +import org.whispersystems.util.Base64; + +import java.io.IOException; + +/** + * An AudioHash is a compact string representation of the wave form and duration for an audio file. + */ +public final class AudioHash { + + @NonNull private final String hash; + @NonNull private final AudioWaveFormData audioWaveForm; + + private AudioHash(@NonNull String hash, @NonNull AudioWaveFormData audioWaveForm) { + this.hash = hash; + this.audioWaveForm = audioWaveForm; + } + + public AudioHash(@NonNull AudioWaveFormData audioWaveForm) { + this(Base64.encodeBytes(audioWaveForm.toByteArray()), audioWaveForm); + } + + public static @Nullable AudioHash parseOrNull(@Nullable String hash) { + if (hash == null) return null; + try { + return new AudioHash(hash, AudioWaveFormData.parseFrom(Base64.decode(hash))); + } catch (IOException e) { + return null; + } + } + + @NonNull AudioWaveFormData getAudioWaveForm() { + return audioWaveForm; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AudioHash other = (AudioHash) o; + return hash.equals(other.hash); + } + + @Override + public int hashCode() { + return hash.hashCode(); + } + + public @NonNull String getHash() { + return hash; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java index 65bb9b9a3df..7943ff19ae3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java @@ -14,11 +14,18 @@ import androidx.annotation.WorkerThread; import androidx.core.util.Consumer; +import com.google.protobuf.ByteString; + +import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.media.DecryptableUriMediaInput; import org.thoughtcrime.securesms.media.MediaInput; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import java.io.IOException; @@ -32,7 +39,7 @@ public final class AudioWaveForm { private static final String TAG = Log.tag(AudioWaveForm.class); - private static final int BARS = 46; + private static final int BAR_COUNT = 46; private static final int SAMPLES_PER_BAR = 4; private final Context context; @@ -43,34 +50,68 @@ public AudioWaveForm(@NonNull Context context, @NonNull AudioSlide slide) { this.slide = slide; } - private static final LruCache WAVE_FORM_CACHE = new LruCache<>(200); - private static final Executor AUDIO_DECODER_EXECUTOR = SignalExecutors.BOUNDED; + private static final LruCache WAVE_FORM_CACHE = new LruCache<>(200); + private static final Executor AUDIO_DECODER_EXECUTOR = new SerialExecutor(SignalExecutors.BOUNDED); @AnyThread - public void generateWaveForm(@NonNull Consumer onSuccess, @NonNull Consumer onFailure) { + public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Consumer onFailure) { + Uri uri = slide.getUri(); + Attachment attachment = slide.asAttachment(); + + if (uri == null) { + Log.w(TAG, "No uri"); + Util.runOnMain(() -> onFailure.accept(null)); + return; + } + + if (!(attachment instanceof DatabaseAttachment)) { + Log.i(TAG, "Not yet in database"); + Util.runOnMain(() -> onFailure.accept(null)); + return; + } + + String cacheKey = uri.toString(); + AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey); + if (cached != null) { + Log.i(TAG, "Loaded wave form from cache " + cacheKey); + Util.runOnMain(() -> onSuccess.accept(cached)); + return; + } + AUDIO_DECODER_EXECUTOR.execute(() -> { - try { - long startTime = System.currentTimeMillis(); - Uri uri = slide.getUri(); - if (uri == null) { - Util.runOnMain(() -> onFailure.accept(null)); - return; - } + AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey); + if (cachedInExecutor != null) { + Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey); + Util.runOnMain(() -> onSuccess.accept(cachedInExecutor)); + return; + } - AudioFileInfo cached = WAVE_FORM_CACHE.get(uri); - if (cached != null) { - Util.runOnMain(() -> onSuccess.accept(cached)); + AudioHash audioHash = attachment.getAudioHash(); + if (audioHash != null) { + AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm()); + if (audioFileInfo.waveForm.length != BAR_COUNT) { + Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey); + } else { + WAVE_FORM_CACHE.put(cacheKey, audioFileInfo); + Log.i(TAG, "Loaded wave form from DB " + cacheKey); + Util.runOnMain(() -> onSuccess.accept(audioFileInfo)); return; } + } - AudioFileInfo fileInfo = generateWaveForm(uri); - WAVE_FORM_CACHE.put(uri, fileInfo); + try { + DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment; + long startTime = System.currentTimeMillis(); + AudioFileInfo fileInfo = generateWaveForm(uri); - Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms", System.currentTimeMillis() - startTime)); + Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey)); + DatabaseFactory.getAttachmentDatabase(context).writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf()); + + WAVE_FORM_CACHE.put(cacheKey, fileInfo); Util.runOnMain(() -> onSuccess.accept(fileInfo)); } catch (IOException e) { - Log.e(TAG, "", e); + Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e); onFailure.accept(e); } }); @@ -83,17 +124,36 @@ public void generateWaveForm(@NonNull Consumer onSuccess, @NonNul */ @WorkerThread @RequiresApi(api = 23) - private AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException { + private @NonNull AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException { try (MediaInput dataSource = DecryptableUriMediaInput.createForUri(context, uri)) { - long[] wave = new long[BARS]; - int[] waveSamples = new int[BARS]; - int[] inputSamples = new int[BARS * SAMPLES_PER_BAR]; + long[] wave = new long[BAR_COUNT]; + int[] waveSamples = new int[BAR_COUNT]; + int[] inputSamples = new int[BAR_COUNT * SAMPLES_PER_BAR]; + + MediaExtractor extractor = dataSource.createExtractor(); - MediaExtractor extractor = dataSource.createExtractor(); - MediaFormat format = extractor.getTrackFormat(0); - long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION); - String mime = requireAudio(format.getString(MediaFormat.KEY_MIME)); - MediaCodec codec = MediaCodec.createDecoderByType(mime); + if (extractor.getTrackCount() == 0) { + throw new IOException("No audio track"); + } + + MediaFormat format = extractor.getTrackFormat(0); + + if (!format.containsKey(MediaFormat.KEY_DURATION)) { + throw new IOException("Unknown duration"); + } + + long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION); + String mime = format.getString(MediaFormat.KEY_MIME); + + if (!mime.startsWith("audio/")) { + throw new IOException("Mime not audio"); + } + + MediaCodec codec = MediaCodec.createDecoderByType(mime); + + if (totalDurationUs == 0) { + throw new IOException("Zero duration"); + } codec.configure(format, null, null, 0); codec.start(); @@ -158,29 +218,24 @@ private AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException { } ByteBuffer buf = codecOutputBuffers[outputBufferIndex]; - int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs) - 1; + int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs); long total = 0; for (int i = 0; i < info.size; i += 2 * 4) { short aShort = buf.getShort(i); total += Math.abs(aShort); } - if (barIndex > 0) { + if (barIndex >= 0 && barIndex < wave.length) { wave[barIndex] += total; waveSamples[barIndex] += info.size / 2; } codec.releaseOutputBuffer(outputBufferIndex, false); if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - Log.d(TAG, "saw output EOS."); sawOutputEOS = true; } } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { codecOutputBuffers = codec.getOutputBuffers(); - Log.d(TAG, "output buffers have changed."); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - MediaFormat oformat = codec.getOutputFormat(); - Log.d(TAG, "output format has changed to " + oformat); - } else { - Log.d(TAG, "dequeueOutputBuffer returned " + outputBufferIndex); + Log.d(TAG, "output format has changed to " + codec.getOutputFormat()); } } while (outputBufferIndex >= 0); } @@ -189,36 +244,46 @@ private AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException { codec.release(); extractor.release(); - float[] floats = new float[AudioWaveForm.BARS]; - float max = 0; - for (int i = 0; i < AudioWaveForm.BARS; i++) { + float[] floats = new float[BAR_COUNT]; + byte[] bytes = new byte[BAR_COUNT]; + float max = 0; + + for (int i = 0; i < BAR_COUNT; i++) { + if (waveSamples[i] == 0) continue; + floats[i] = wave[i] / (float) waveSamples[i]; if (floats[i] > max) { max = floats[i]; } } - for (int i = 0; i < AudioWaveForm.BARS; i++) { - floats[i] /= max; + + for (int i = 0; i < BAR_COUNT; i++) { + float normalized = floats[i] / max; + bytes[i] = (byte) (255 * normalized); } - return new AudioFileInfo(totalDurationUs, floats); - } - } - private static @NonNull String requireAudio(@NonNull String mime) { - if (!mime.startsWith("audio/")) { - throw new AssertionError(); + return new AudioFileInfo(totalDurationUs, bytes); } - - return mime; } public static class AudioFileInfo { private final long durationUs; + private final byte[] waveFormBytes; private final float[] waveForm; - private AudioFileInfo(long durationUs, float[] waveForm) { - this.durationUs = durationUs; - this.waveForm = waveForm; + private static @NonNull AudioFileInfo fromDatabaseProtobuf(@NonNull AudioWaveFormData audioWaveForm) { + return new AudioFileInfo(audioWaveForm.getDurationUs(), audioWaveForm.getWaveForm().toByteArray()); + } + + private AudioFileInfo(long durationUs, byte[] waveFormBytes) { + this.durationUs = durationUs; + this.waveFormBytes = waveFormBytes; + this.waveForm = new float[waveFormBytes.length]; + + for (int i = 0; i < waveFormBytes.length; i++) { + int unsigned = waveFormBytes[i] & 0xff; + this.waveForm[i] = unsigned / 255f; + } } public long getDuration(@NonNull TimeUnit timeUnit) { @@ -228,5 +293,12 @@ public long getDuration(@NonNull TimeUnit timeUnit) { public float[] getWaveForm() { return waveForm; } + + private @NonNull AudioWaveFormData toDatabaseProtobuf() { + return AudioWaveFormData.newBuilder() + .setDurationUs(durationUs) + .setWaveForm(ByteString.copyFrom(waveFormBytes)) + .build(); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java index 44ae1fe3cb5..ed307ff0beb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java @@ -8,7 +8,6 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.SeekBar; @@ -38,7 +37,7 @@ import org.thoughtcrime.securesms.mms.SlideClickListener; import java.io.IOException; -import java.util.Locale; +import java.util.Objects; import java.util.concurrent.TimeUnit; public final class AudioView extends FrameLayout implements AudioSlidePlayer.Listener { @@ -49,7 +48,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis private static final int REVERSE = -1; @NonNull private final AnimatingToggle controlToggle; - @NonNull private final ViewGroup container; @NonNull private final View progressAndPlay; @NonNull private final LottieAnimationView playPauseButton; @NonNull private final ImageView downloadButton; @@ -58,7 +56,6 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis private final boolean smallView; private final boolean autoRewind; - @Nullable private final TextView timestamp; @Nullable private final TextView duration; @ColorInt private final int waveFormPlayedBarsColor; @@ -69,6 +66,7 @@ public final class AudioView extends FrameLayout implements AudioSlidePlayer.Lis private int backwardsCounter; private int lottieDirection; private boolean isPlaying; + private long durationMillis; public AudioView(Context context) { this(context, null); @@ -89,14 +87,12 @@ public AudioView(Context context, AttributeSet attrs, int defStyleAttr) { inflate(context, smallView ? R.layout.audio_view_small : R.layout.audio_view, this); - this.container = findViewById(R.id.audio_widget_container); this.controlToggle = findViewById(R.id.control_toggle); this.playPauseButton = findViewById(R.id.play); this.progressAndPlay = findViewById(R.id.progress_and_play); this.downloadButton = findViewById(R.id.download); this.circleProgress = findViewById(R.id.circle_progress); this.seekBar = findViewById(R.id.seek); - this.timestamp = findViewById(R.id.timestamp); this.duration = findViewById(R.id.duration); lottieDirection = REVERSE; @@ -108,8 +104,6 @@ public AudioView(Context context, AttributeSet attrs, int defStyleAttr) { this.waveFormPlayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformPlayedBarsColor, Color.WHITE); this.waveFormUnplayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformUnplayedBarsColor, Color.WHITE); - - container.setBackgroundColor(typedArray.getColor(R.styleable.AudioView_widgetBackground, Color.TRANSPARENT)); } finally { if (typedArray != null) { typedArray.recycle(); @@ -132,6 +126,14 @@ protected void onDetachedFromWindow() { public void setAudio(final @NonNull AudioSlide audio, final boolean showControls) { + if (seekBar instanceof WaveFormSeekBarView) { + if (audioSlidePlayer != null && !Objects.equals(audioSlidePlayer.getAudioSlide().getUri(), audio.getUri())) { + WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar; + waveFormView.setWaveMode(false); + seekBar.setProgress(0); + durationMillis = 0; + } + } if (showControls && audio.isPendingDownload()) { controlToggle.displayQuick(downloadButton); @@ -157,14 +159,14 @@ public void setAudio(final @NonNull AudioSlide audio, WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar; waveFormView.setColors(waveFormPlayedBarsColor, waveFormUnplayedBarsColor); if (android.os.Build.VERSION.SDK_INT >= 23) { - new AudioWaveForm(getContext(), audio).generateWaveForm( + new AudioWaveForm(getContext(), audio).getWaveForm( data -> { - waveFormView.setWaveData(data.getWaveForm()); if (duration != null) { - long durationSecs = data.getDuration(TimeUnit.SECONDS); - duration.setText(getContext().getResources().getString(R.string.AudioView_duration, durationSecs / 60, durationSecs % 60)); + durationMillis = data.getDuration(TimeUnit.MILLISECONDS); + updateProgress(0, 0); duration.setVisibility(VISIBLE); } + waveFormView.setWaveData(data.getWaveForm()); }, e -> waveFormView.setWaveMode(false)); } else { @@ -243,10 +245,9 @@ public void onProgress(double progress, long millis) { } private void updateProgress(float progress, long millis) { - if (timestamp != null) { - timestamp.setText(String.format(Locale.getDefault(), "%02d:%02d", - TimeUnit.MILLISECONDS.toMinutes(millis), - TimeUnit.MILLISECONDS.toSeconds(millis))); + if (duration != null && durationMillis > 0) { + long remainingSecs = TimeUnit.MILLISECONDS.toSeconds(durationMillis - millis); + duration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60)); } if (smallView) { @@ -262,9 +263,6 @@ public void setTint(int foregroundTint, int backgroundTint) { this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN); this.circleProgress.setBarColor(foregroundTint); - if (this.timestamp != null) { - this.timestamp.setTextColor(foregroundTint); - } if (this.duration != null) { this.duration.setTextColor(foregroundTint); } @@ -372,7 +370,12 @@ private class SeekBarModifiedListener implements SeekBar.OnSeekBarChangeListener private boolean wasPlaying; @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {} + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && durationMillis > 0) { + float progressFloat = progress / (float) seekBar.getMax(); + updateProgress(progressFloat, (long) (durationMillis * progressFloat)); + } + } @Override public synchronized void onStartTrackingTouch(SeekBar seekBar) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/WaveFormSeekBarView.java b/app/src/main/java/org/thoughtcrime/securesms/components/WaveFormSeekBarView.java index 46f40505ec5..a63d9d967e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/WaveFormSeekBarView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/WaveFormSeekBarView.java @@ -88,9 +88,8 @@ public void setWaveData(@NonNull float[] data) { if (!Arrays.equals(data, this.data)) { this.data = data; this.dataSetTime = System.currentTimeMillis(); - setWaveMode(data.length > 0); - invalidate(); } + setWaveMode(data.length > 0); } public void setWaveMode(boolean waveMode) { @@ -101,7 +100,9 @@ public void setWaveMode(boolean waveMode) { @Override protected void onDraw(Canvas canvas) { - drawWave(canvas); + if (waveMode) { + drawWave(canvas); + } super.onDraw(canvas); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java index aef1fdf4c85..b6119620d79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java @@ -643,7 +643,7 @@ public int describeContents() { private static Attachment attachmentFromUri(@Nullable Uri uri) { if (uri == null) return null; - return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, false, false, null, null, null, null); + return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, false, false, null, null, null, null, null); } @Override 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 0f67f9c8f3d..de60295b276 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -31,6 +31,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; import com.bumptech.glide.Glide; import com.fasterxml.jackson.annotation.JsonCreator; @@ -44,12 +45,14 @@ import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; +import org.thoughtcrime.securesms.audio.AudioHash; import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.MediaStream; import org.thoughtcrime.securesms.mms.MmsException; @@ -120,7 +123,7 @@ public class AttachmentDatabase extends Database { static final String HEIGHT = "height"; static final String CAPTION = "caption"; private static final String DATA_HASH = "data_hash"; - static final String BLUR_HASH = "blur_hash"; + static final String VISUAL_HASH = "blur_hash"; static final String TRANSFORM_PROPERTIES = "transform_properties"; static final String DISPLAY_ORDER = "display_order"; static final String UPLOAD_TIMESTAMP = "upload_timestamp"; @@ -145,7 +148,7 @@ public class AttachmentDatabase extends Database { THUMBNAIL_ASPECT_RATIO, UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE, QUOTE, DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID, - STICKER_PACK_KEY, STICKER_ID, DATA_HASH, BLUR_HASH, + STICKER_PACK_KEY, STICKER_ID, DATA_HASH, VISUAL_HASH, TRANSFORM_PROPERTIES, TRANSFER_FILE, DISPLAY_ORDER, UPLOAD_TIMESTAMP }; @@ -182,7 +185,7 @@ public class AttachmentDatabase extends Database { STICKER_PACK_KEY + " DEFAULT NULL, " + STICKER_ID + " INTEGER DEFAULT -1, " + DATA_HASH + " TEXT DEFAULT NULL, " + - BLUR_HASH + " TEXT DEFAULT NULL, " + + VISUAL_HASH + " TEXT DEFAULT NULL, " + TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + TRANSFER_FILE + " TEXT DEFAULT NULL, " + DISPLAY_ORDER + " INTEGER DEFAULT 0, " + @@ -417,7 +420,7 @@ public void deleteAttachmentFilesForViewOnceMessage(long mmsId) { values.put(WIDTH, 0); values.put(HEIGHT, 0); values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); - values.put(BLUR_HASH, (String) null); + values.put(VISUAL_HASH, (String) null); values.put(CONTENT_TYPE, MediaUtil.VIEW_ONCE); database.update(TABLE_NAME, values, MMS_ID + " = ?", new String[] {mmsId + ""}); @@ -530,8 +533,9 @@ public void insertAttachmentsForPlaceholder(long mmsId, @NonNull AttachmentId at values.put(DATA_HASH, dataInfo.hash); } - if (placeholder != null && placeholder.getBlurHash() != null) { - values.put(BLUR_HASH, placeholder.getBlurHash().getHash()); + String visualHashString = getVisualHashStringOrNull(placeholder); + if (visualHashString != null) { + values.put(VISUAL_HASH, visualHashString); } values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); @@ -555,9 +559,11 @@ public void insertAttachmentsForPlaceholder(long mmsId, @NonNull AttachmentId at thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, STANDARD_THUMB_TIME)); } - private static @Nullable String getBlurHashStringOrNull(@Nullable BlurHash blurHash) { - if (blurHash == null) return null; - return blurHash.getHash(); + private static @Nullable String getVisualHashStringOrNull(@Nullable Attachment attachment) { + if (attachment == null) return null; + else if (attachment.getBlurHash() != null) return attachment.getBlurHash().getHash(); + else if (attachment.getAudioHash() != null) return attachment.getAudioHash().getHash(); + else return null; } public void copyAttachmentData(@NonNull AttachmentId sourceId, @NonNull AttachmentId destinationId) @@ -594,7 +600,7 @@ public void copyAttachmentData(@NonNull AttachmentId sourceId, @NonNull Attachme contentValues.put(WIDTH, sourceAttachment.getWidth()); contentValues.put(HEIGHT, sourceAttachment.getHeight()); contentValues.put(CONTENT_TYPE, sourceAttachment.getContentType()); - contentValues.put(BLUR_HASH, getBlurHashStringOrNull(sourceAttachment.getBlurHash())); + contentValues.put(VISUAL_HASH, getVisualHashStringOrNull(sourceAttachment)); database.update(TABLE_NAME, contentValues, PART_ID_WHERE, destinationId.toStrings()); } @@ -638,7 +644,7 @@ public void updateAttachmentAfterUpload(@NonNull AttachmentId id, @NonNull Attac values.put(NAME, attachment.getRelay()); values.put(SIZE, attachment.getSize()); values.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId()); - values.put(BLUR_HASH, getBlurHashStringOrNull(attachment.getBlurHash())); + values.put(VISUAL_HASH, getVisualHashStringOrNull(attachment)); values.put(UPLOAD_TIMESTAMP, uploadTimestamp); if (dataInfo != null && dataInfo.hash != null) { @@ -1099,11 +1105,12 @@ public List getAttachment(@NonNull Cursor cursor) { JsonUtils.SaneJSONObject object = new JsonUtils.SaneJSONObject(array.getJSONObject(i)); if (!object.isNull(ROW_ID)) { + String contentType = object.getString(CONTENT_TYPE); result.add(new DatabaseAttachment(new AttachmentId(object.getLong(ROW_ID), object.getLong(UNIQUE_ID)), object.getLong(MMS_ID), !TextUtils.isEmpty(object.getString(DATA)), !TextUtils.isEmpty(object.getString(THUMBNAIL)), - object.getString(CONTENT_TYPE), + contentType, object.getInt(TRANSFER_STATE), object.getLong(SIZE), object.getString(FILE_NAME), @@ -1123,7 +1130,8 @@ public List getAttachment(@NonNull Cursor cursor) { object.getString(STICKER_PACK_KEY), object.getInt(STICKER_ID)) : null, - BlurHash.parseOrNull(object.getString(BLUR_HASH)), + MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(object.getString(VISUAL_HASH)), + MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(object.getString(VISUAL_HASH)) : null, TransformProperties.parse(object.getString(TRANSFORM_PROPERTIES)), object.getInt(DISPLAY_ORDER), object.getLong(UPLOAD_TIMESTAMP))); @@ -1132,12 +1140,13 @@ public List getAttachment(@NonNull Cursor cursor) { return result; } else { + String contentType = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)); return Collections.singletonList(new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))), cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), !cursor.isNull(cursor.getColumnIndexOrThrow(DATA)), !cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)), - cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)), + contentType, cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)), @@ -1157,7 +1166,8 @@ public List getAttachment(@NonNull Cursor cursor) { cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_KEY)), cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID))) : null, - BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(BLUR_HASH))), + MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))), + MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null, TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))), cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)), cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP)))); @@ -1167,7 +1177,6 @@ public List getAttachment(@NonNull Cursor cursor) { } } - private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote) throws MmsException { @@ -1219,11 +1228,11 @@ private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean contentValues.put(CAPTION, attachment.getCaption()); contentValues.put(UPLOAD_TIMESTAMP, useTemplateUpload ? template.getUploadTimestamp() : attachment.getUploadTimestamp()); if (attachment.getTransformProperties().isVideoEdited()) { - contentValues.putNull(BLUR_HASH); + contentValues.putNull(VISUAL_HASH); contentValues.put(TRANSFORM_PROPERTIES, attachment.getTransformProperties().serialize()); thumbnailTimeUs = Math.max(STANDARD_THUMB_TIME, attachment.getTransformProperties().videoTrimStartTimeUs); } else { - contentValues.put(BLUR_HASH, getBlurHashStringOrNull(template.getBlurHash())); + contentValues.put(VISUAL_HASH, getVisualHashStringOrNull(template)); contentValues.put(TRANSFORM_PROPERTIES, template.getTransformProperties().serialize()); thumbnailTimeUs = STANDARD_THUMB_TIME; } @@ -1330,6 +1339,21 @@ protected void updateAttachmentThumbnail(AttachmentId attachmentId, InputStream } } + @WorkerThread + public void writeAudioHash(@NonNull AttachmentId attachmentId, @Nullable AudioWaveFormData audioWaveForm) { + Log.i(TAG, "updating part audio wave form for #" + attachmentId); + + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + ContentValues values = new ContentValues(1); + + if (audioWaveForm != null) { + values.put(VISUAL_HASH, new AudioHash(audioWaveForm).getHash()); + } else { + values.putNull(VISUAL_HASH); + } + + database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); + } @VisibleForTesting class ThumbnailFetchCallable implements Callable { 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 05bdb8804a1..4bbc41350d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -43,7 +43,7 @@ public class MediaDatabase extends Database { + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " - + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " + + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CAPTION + ", " 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 6e73c6d458c..6b6b6c71421 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -218,7 +218,7 @@ public class MmsDatabase extends MessagingDatabase { "'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID+ ", " + "'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " + "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + - "'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " + + "'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " + "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + 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 d8b10193f53..8daa21c9a7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.util.Pair; @@ -387,7 +386,7 @@ private Cursor queryTables(String[] projection, String selection, String order, "'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", " + "'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " + "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + - "'" + AttachmentDatabase.BLUR_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BLUR_HASH + ", " + + "'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " + "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java index 4ee6c1a245d..d44ec0e13af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java @@ -138,7 +138,7 @@ private static GroupActionResult sendGroupUpdate(@NonNull Context cont if (avatar != null) { Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory(); - avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null); + avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null, null); } OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index c089a668517..6df3ed356ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -229,7 +229,7 @@ private void storeRetrievedMms(String contentLocation, attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()), AttachmentDatabase.TRANSFER_PROGRESS_DONE, - part.getData().length, name, false, false, null, null, null, null)); + part.getData().length, name, false, false, null, null, null, null, null)); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index 09a8fbcab0e..41c59a1b754 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -1634,6 +1634,7 @@ private Optional getStickerAttachment(Optional tasks = new ArrayDeque<>(); + private final Executor executor; + private Runnable active; + + public SerialExecutor(@NonNull Executor executor) { + this.executor = executor; + } + + public synchronized void execute(final Runnable r) { + tasks.offer(() -> { + try { + r.run(); + } finally { + scheduleNext(); + } + }); + if (active == null) { + scheduleNext(); + } + } + + private synchronized void scheduleNext() { + if ((active = tasks.poll()) != null) { + executor.execute(active); + } + } +} \ No newline at end of file diff --git a/app/src/main/proto/Database.proto b/app/src/main/proto/Database.proto index 1831b702bad..5da6e253bfa 100644 --- a/app/src/main/proto/Database.proto +++ b/app/src/main/proto/Database.proto @@ -41,3 +41,8 @@ message TemporalAuthCredentialResponse { message TemporalAuthCredentialResponses { repeated TemporalAuthCredentialResponse credentialResponse = 1; } + +message AudioWaveFormData { + int64 durationUs = 1; + bytes waveForm = 2; +} diff --git a/app/src/main/res/layout/audio_view.xml b/app/src/main/res/layout/audio_view.xml index 69f71384263..77da883d1cf 100644 --- a/app/src/main/res/layout/audio_view.xml +++ b/app/src/main/res/layout/audio_view.xml @@ -4,58 +4,36 @@ tools:context="org.thoughtcrime.securesms.components.AudioView"> - + - - - - - - - + diff --git a/app/src/main/res/layout/audio_view_small.xml b/app/src/main/res/layout/audio_view_small.xml index 9e958b31de1..44a07c74d77 100644 --- a/app/src/main/res/layout/audio_view_small.xml +++ b/app/src/main/res/layout/audio_view_small.xml @@ -4,7 +4,6 @@ tools:context="org.thoughtcrime.securesms.components.AudioView"> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index f03266ef021..06e4e49c807 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -336,7 +336,6 @@ -