diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
index 617d22b04f..ff341da3cd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java
@@ -16,6 +16,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -33,9 +34,9 @@ import java.util.Optional;
/**
* Encapsulates control of voice note playback from an Activity component.
- *
+ *
* This class assumes that it will be created within the scope of Activity#onCreate
- *
+ *
* The workhorse of this repository is the ProgressEventHandler, which will supply a
* steady stream of update events to the set callback.
*/
@@ -54,15 +55,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
private MutableLiveData voiceNotePlaybackState = new MutableLiveData<>(VoiceNotePlaybackState.NONE);
private LiveData> voiceNotePlayerViewState;
private VoiceNoteProximityWakeLockManager voiceNoteProximityWakeLockManager;
+ private boolean isMediaBrowserCreationPostponed;
private final MediaControllerCompatCallback mediaControllerCompatCallback = new MediaControllerCompatCallback();
public VoiceNoteMediaController(@NonNull FragmentActivity activity) {
- this.activity = activity;
- this.mediaBrowser = new MediaBrowserCompat(activity,
- new ComponentName(activity, VoiceNotePlaybackService.class),
- new ConnectionCallback(),
- null);
+ this(activity, false);
+ }
+
+ public VoiceNoteMediaController(@NonNull FragmentActivity activity, boolean postponeMediaBrowserCreation) {
+ this.activity = activity;
+ this.isMediaBrowserCreationPostponed = postponeMediaBrowserCreation;
activity.getLifecycle().addObserver(this);
@@ -71,9 +74,9 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
VoiceNotePlaybackState.ClipType.Message message = (VoiceNotePlaybackState.ClipType.Message) playbackState.getClipType();
LiveRecipient sender = Recipient.live(message.getSenderId());
LiveRecipient threadRecipient = Recipient.live(message.getThreadRecipientId());
- LiveData name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(),
- threadRecipient.getLiveDataResolved(),
- (s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null));
+ LiveData name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(),
+ threadRecipient.getLiveDataResolved(),
+ (s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null));
return Transformations.map(name, displayName -> Optional.of(
new VoiceNotePlayerView.State(
@@ -95,6 +98,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
});
}
+ public void ensureMediaBrowser() {
+ if (mediaBrowser != null) {
+ return;
+ }
+
+ mediaBrowser = new MediaBrowserCompat(activity,
+ new ComponentName(activity, VoiceNotePlaybackService.class),
+ new ConnectionCallback(),
+ null);
+ }
+
public LiveData getVoiceNotePlaybackState() {
return voiceNotePlaybackState;
}
@@ -103,8 +117,22 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
return voiceNotePlayerViewState;
}
+ public void finishPostpone() {
+ isMediaBrowserCreationPostponed = false;
+ if (activity != null && mediaBrowser == null && activity.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
+ ensureMediaBrowser();
+ mediaBrowser.disconnect();
+ mediaBrowser.connect();
+ }
+ }
+
@Override
public void onResume(@NonNull LifecycleOwner owner) {
+ if (mediaBrowser == null && isMediaBrowserCreationPostponed) {
+ return;
+ }
+
+ ensureMediaBrowser();
mediaBrowser.disconnect();
mediaBrowser.connect();
}
@@ -117,7 +145,9 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
MediaControllerCompat.getMediaController(activity).unregisterCallback(mediaControllerCompatCallback);
}
- mediaBrowser.disconnect();
+ if (mediaBrowser != null) {
+ mediaBrowser.disconnect();
+ }
}
@Override
@@ -201,8 +231,8 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
* Tells the Media service to resume playback of a given audio slide. If the audio slide is not
* currently paused, playback will be started from the beginning.
*
- * @param audioSlideUri The Uri of the desired audio slide
- * @param messageId The Message id of the given audio slide
+ * @param audioSlideUri The Uri of the desired audio slide
+ * @param messageId The Message id of the given audio slide
*/
public void resumePlayback(@NonNull Uri audioSlideUri, long messageId) {
if (getMediaController() == null) {
@@ -390,8 +420,8 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
}
private static boolean canExtractPlaybackInformationFromMetadata(@Nullable MediaMetadataCompat mediaMetadataCompat) {
- return mediaMetadataCompat != null &&
- mediaMetadataCompat.getDescription() != null &&
+ return mediaMetadataCompat != null &&
+ mediaMetadataCompat.getDescription() != null &&
mediaMetadataCompat.getDescription().getMediaUri() != null;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
index b9a17ec690..bffc9ab958 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java
@@ -254,7 +254,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
if (extras == null) {
return;
}
- long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
+ long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaItemFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID));
MessageTable messageDatabase = SignalDatabase.messages();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt
index b88fdd5d81..e109b5bf3f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt
@@ -16,9 +16,11 @@ import org.thoughtcrime.securesms.components.reminder.ReminderView
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.util.Debouncer
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.views.Stub
+import java.util.concurrent.TimeUnit
open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback, DonationPaymentComponent {
@@ -26,6 +28,7 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
private const val STATE_WATERMARK = "share_data_watermark"
}
+ private val transitionDebouncer: Debouncer = Debouncer(150, TimeUnit.MILLISECONDS)
private lateinit var fragment: ConversationParentFragment
private var shareDataTimestamp: Long = -1L
@@ -35,6 +38,8 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ supportPostponeEnterTransition()
+ transitionDebouncer.publish { supportStartPostponedEnterTransition() }
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
if (savedInstanceState != null) {
@@ -51,6 +56,11 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ transitionDebouncer.clear()
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(STATE_WATERMARK, shareDataTimestamp)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index 2d89f4bd03..a05ebac07c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -746,6 +746,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
startupStopwatch.split("first-render");
startupStopwatch.stop(TAG);
SignalLocalMetrics.ConversationOpen.onRenderFinished();
+ listener.onFirstRender();
});
}
});
@@ -1480,6 +1481,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
@NonNull ConversationReactionOverlay.OnHideListener onHideListener);
void onCursorChanged();
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
+ void onFirstRender();
void onVoiceNotePause(@NonNull Uri uri);
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress);
void onVoiceNoteResume(@NonNull Uri uri, long messageId);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
index 8b0a90e3d0..1b7ae9a637 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
@@ -512,7 +512,7 @@ public class ConversationParentFragment extends Fragment
return;
}
- voiceNoteMediaController = new VoiceNoteMediaController(requireActivity());
+ voiceNoteMediaController = new VoiceNoteMediaController(requireActivity(), true);
voiceRecorderWakeLock = new VoiceRecorderWakeLock(requireActivity());
// TODO [alex] LargeScreenSupport -- Should be removed once we move to multi-pane layout.
@@ -4040,6 +4040,12 @@ public class ConversationParentFragment extends Fragment
}
}
+ @Override
+ public void onFirstRender() {
+ requireActivity().supportStartPostponedEnterTransition();
+ voiceNoteMediaController.finishPostpone();
+ }
+
@Override
public void onVoiceNotePause(@NonNull Uri uri) {
voiceNoteMediaController.pausePlayback(uri);