Fix composer voice memo cancellation due to focus loss.

This commit is contained in:
Nicholas
2023-02-02 16:11:59 -05:00
committed by Nicholas Tinsley
parent 63a153571d
commit d33aa247db
5 changed files with 54 additions and 33 deletions

View File

@@ -7,6 +7,7 @@ import android.os.Build;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
@@ -27,6 +28,7 @@ public class AudioRecorder {
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder"); private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
private final Context context; private final Context context;
private final AudioRecordingHandler uiHandler;
private final AudioRecorderFocusManager audioFocusManager; private final AudioRecorderFocusManager audioFocusManager;
private Recorder recorder; private Recorder recorder;
@@ -34,12 +36,28 @@ public class AudioRecorder {
private SingleSubject<VoiceNoteDraft> recordingSubject; private SingleSubject<VoiceNoteDraft> recordingSubject;
public AudioRecorder(@NonNull Context context) { public AudioRecorder(@NonNull Context context, @Nullable AudioRecordingHandler uiHandler) {
this.context = context; this.context = context;
audioFocusManager = AudioRecorderFocusManager.create(context, focusChange -> { this.uiHandler = uiHandler;
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
stopRecording(); AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener;
});
if (this.uiHandler != null) {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
this.uiHandler.onRecordCanceled(false);
}
};
} else {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
stopRecording();
}
};
}
audioFocusManager = AudioRecorderFocusManager.create(context, onAudioFocusChangeListener);
} }
public @NonNull Single<VoiceNoteDraft> startRecording() { public @NonNull Single<VoiceNoteDraft> startRecording() {
@@ -59,7 +77,7 @@ public class AudioRecorder {
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0) .forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
.withMimeType(MediaUtil.AUDIO_AAC) .withMimeType(MediaUtil.AUDIO_AAC)
.createForDraftAttachmentAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e)); .createForDraftAttachmentAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e));
recorder = Build.VERSION.SDK_INT >= 26 ? new MediaRecorderWrapper() : new AudioCodec(); recorder = Build.VERSION.SDK_INT >= 26 ? new MediaRecorderWrapper() : new AudioCodec();
int focusResult = audioFocusManager.requestAudioFocus(); int focusResult = audioFocusManager.requestAudioFocus();
if (focusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (focusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.w(TAG, "Could not gain audio focus. Received result code " + focusResult); Log.w(TAG, "Could not gain audio focus. Received result code " + focusResult);

View File

@@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.audio
interface AudioRecordingHandler {
fun onRecordPressed()
fun onRecordReleased()
fun onRecordCanceled(byUser: Boolean)
fun onRecordLocked()
fun onRecordMoved(offsetX: Float, absoluteX: Float)
fun onRecordPermissionRequired()
}

View File

@@ -33,6 +33,7 @@ import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener; import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.audio.AudioRecordingHandler;
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener; import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle; import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
@@ -63,7 +64,7 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class InputPanel extends LinearLayout public class InputPanel extends LinearLayout
implements MicrophoneRecorderView.Listener, implements AudioRecordingHandler,
KeyboardAwareLinearLayout.OnKeyboardShownListener, KeyboardAwareLinearLayout.OnKeyboardShownListener,
EmojiEventListener, EmojiEventListener,
ConversationStickerSuggestionAdapter.EventListener ConversationStickerSuggestionAdapter.EventListener
@@ -137,7 +138,7 @@ public class InputPanel extends LinearLayout
this.voiceNoteDraftView = findViewById(R.id.voice_note_draft_view); this.voiceNoteDraftView = findViewById(R.id.voice_note_draft_view);
this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel)); this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel));
this.microphoneRecorderView = findViewById(R.id.recorder_view); this.microphoneRecorderView = findViewById(R.id.recorder_view);
this.microphoneRecorderView.setListener(this); this.microphoneRecorderView.setHandler(this);
this.recordTime = new RecordTime(findViewById(R.id.record_time), this.recordTime = new RecordTime(findViewById(R.id.record_time),
findViewById(R.id.microphone), findViewById(R.id.microphone),
TimeUnit.HOURS.toSeconds(1), TimeUnit.HOURS.toSeconds(1),

View File

@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.audio.AudioRecordingHandler;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@@ -34,10 +35,10 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
public static final int ANIMATION_DURATION = 200; public static final int ANIMATION_DURATION = 200;
private FloatingRecordButton floatingRecordButton; private FloatingRecordButton floatingRecordButton;
private LockDropTarget lockDropTarget; private LockDropTarget lockDropTarget;
private @Nullable Listener listener; private @Nullable AudioRecordingHandler handler;
private @NonNull State state = State.NOT_RUNNING; private @NonNull State state = State.NOT_RUNNING;
public MicrophoneRecorderView(Context context) { public MicrophoneRecorderView(Context context) {
super(context); super(context);
@@ -63,8 +64,8 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
state = State.NOT_RUNNING; state = State.NOT_RUNNING;
hideUi(); hideUi();
if (listener != null) { if (handler != null) {
listener.onRecordCanceled(byUser); handler.onRecordCanceled(byUser);
} }
} }
} }
@@ -78,7 +79,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
state = State.RUNNING_LOCKED; state = State.RUNNING_LOCKED;
hideUi(); hideUi();
if (listener != null) listener.onRecordLocked(); if (handler != null) handler.onRecordLocked();
} }
} }
@@ -87,7 +88,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
state = State.NOT_RUNNING; state = State.NOT_RUNNING;
hideUi(); hideUi();
if (listener != null) listener.onRecordReleased(); if (handler != null) handler.onRecordReleased();
} }
} }
@@ -101,12 +102,12 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) { if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
if (listener != null) listener.onRecordPermissionRequired(); if (handler != null) handler.onRecordPermissionRequired();
} else if (state == State.NOT_RUNNING) { } else if (state == State.NOT_RUNNING) {
state = State.RUNNING_HELD; state = State.RUNNING_HELD;
floatingRecordButton.display(event.getX(), event.getY()); floatingRecordButton.display(event.getX(), event.getY());
lockDropTarget.display(); lockDropTarget.display();
if (listener != null) listener.onRecordPressed(); if (handler != null) handler.onRecordPressed();
} }
break; break;
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
@@ -114,13 +115,13 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
if (this.state == State.RUNNING_HELD) { if (this.state == State.RUNNING_HELD) {
state = State.NOT_RUNNING; state = State.NOT_RUNNING;
hideUi(); hideUi();
if (listener != null) listener.onRecordReleased(); if (handler != null) handler.onRecordReleased();
} }
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
if (this.state == State.RUNNING_HELD) { if (this.state == State.RUNNING_HELD) {
this.floatingRecordButton.moveTo(event.getX(), event.getY()); this.floatingRecordButton.moveTo(event.getX(), event.getY());
if (listener != null) listener.onRecordMoved(floatingRecordButton.lastOffsetX, event.getRawX()); if (handler != null) handler.onRecordMoved(floatingRecordButton.lastOffsetX, event.getRawX());
int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target); int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target);
if (floatingRecordButton.lastOffsetY <= dimensionPixelSize) { if (floatingRecordButton.lastOffsetY <= dimensionPixelSize) {
@@ -133,17 +134,8 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
return false; return false;
} }
public void setListener(@Nullable Listener listener) { public void setHandler(@Nullable AudioRecordingHandler handler) {
this.listener = listener; this.handler = handler;
}
public interface Listener {
void onRecordPressed();
void onRecordReleased();
void onRecordCanceled(boolean byUser);
void onRecordLocked();
void onRecordMoved(float offsetX, float absoluteX);
void onRecordPermissionRequired();
} }
private static class FloatingRecordButton { private static class FloatingRecordButton {

View File

@@ -2071,7 +2071,7 @@ public class ConversationParentFragment extends Fragment
inputPanel.setMediaListener(this); inputPanel.setMediaListener(this);
attachmentManager = new AttachmentManager(requireContext(), view, this); attachmentManager = new AttachmentManager(requireContext(), view, this);
audioRecorder = new AudioRecorder(requireContext()); audioRecorder = new AudioRecorder(requireContext(), inputPanel);
typingTextWatcher = new ComposeTextWatcher(); typingTextWatcher = new ComposeTextWatcher();
SendButtonListener sendButtonListener = new SendButtonListener(); SendButtonListener sendButtonListener = new SendButtonListener();