mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
Fix composer voice memo cancellation due to focus loss.
This commit is contained in:
committed by
Nicholas Tinsley
parent
63a153571d
commit
d33aa247db
@@ -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);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user