1
0
Fork 1

Improve voice recorder

(cherry picked from commit 239f7d824d)
This commit is contained in:
12aw 2024-01-14 00:09:34 +01:00
parent af454827cf
commit 835ed2192b
6 changed files with 378 additions and 25 deletions

View file

@ -41,19 +41,6 @@
"versionName": "1.7.8.6",
"outputFile": "monocles chat-1.7.8.6-git-x86-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 15701,
"versionName": "1.7.8.6",
"outputFile": "monocles chat-1.7.8.6-git-armeabi-v7a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
@ -66,6 +53,19 @@
"versionCode": 15703,
"versionName": "1.7.8.6",
"outputFile": "monocles chat-1.7.8.6-git-x86_64-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 15701,
"versionName": "1.7.8.6",
"outputFile": "monocles chat-1.7.8.6-git-armeabi-v7a-release.apk"
}
],
"elementType": "File"

View file

@ -243,6 +243,7 @@
android:launchMode="singleTask"
android:minWidth="336dp"
android:minHeight="480dp"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".ui.ScanActivity"
@ -450,13 +451,6 @@
android:name="android.service.chooser.chooser_target_service"
android:value="eu.siacs.conversations.services.ContactChooserTargetService" />
</activity>
<activity
android:name=".ui.RecordingActivity"
android:autoRemoveFromRecents="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleInstance"
android:supportsPictureInPicture="true"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".ui.ShareLocationActivity"
android:label="@string/share_location"

View file

@ -4,6 +4,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.Map;
import de.monocles.chat.KeyboardHeightProvider;
@ -13,11 +15,14 @@ import eu.siacs.conversations.utils.Random;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static eu.siacs.conversations.persistance.FileBackend.APP_DIRECTORY;
import static eu.siacs.conversations.persistance.FileBackend.SENT_AUDIOS;
import static eu.siacs.conversations.ui.SettingsActivity.HIDE_YOU_ARE_NOT_PARTICIPATING;
import static eu.siacs.conversations.ui.SettingsActivity.WARN_UNENCRYPTED_CHAT;
import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT;
@ -64,9 +69,11 @@ import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@ -160,6 +167,8 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -242,12 +251,74 @@ import androidx.emoji2.emojipicker.EmojiPickerView;
import androidx.emoji2.emojipicker.RecentEmojiAsyncProvider;
import androidx.emoji2.emojipicker.RecentEmojiProviderAdapter;
import static eu.siacs.conversations.persistance.FileBackend.SENT_AUDIOS;
import static eu.siacs.conversations.utils.StorageHelper.getConversationsDirectory;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.FragmentConversationBinding;
import eu.siacs.conversations.utils.ThemeHelper;
import eu.siacs.conversations.utils.TimeFrameUtils;
import me.drakeet.support.toast.ToastCompat;
public class ConversationFragment extends XmppFragment
implements EditMessage.KeyboardListener,
MessageAdapter.OnContactPictureLongClicked,
MessageAdapter.OnContactPictureClicked,
MessageAdapter.OnInlineImageLongClicked {
//Voice recoder
private MediaRecorder mRecorder;
private Integer oldOrientation;
private long mStartTime = 0;
private boolean alternativeCodec = false;
private boolean recording = false;
private CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
private Handler mHandler = new Handler();
private Runnable mTickExecutor = new Runnable() {
@Override
public void run() {
tick();
mHandler.postDelayed(mTickExecutor, 100);
}
};
private File mOutputFile;
private FileObserver mFileObserver;
public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
@ -767,6 +838,27 @@ public class ConversationFragment extends XmppFragment
}
};
private final OnClickListener mCancelVoiceRecord = new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.removeCallbacks(mTickExecutor);
stopRecording(false);
activity.setResult(RESULT_CANCELED);
//activity.finish();
binding.recordingVoiceActivity.setVisibility(View.GONE);
}
};
private final OnClickListener mShareVoiceRecord = new OnClickListener() {
@Override
public void onClick(View v) {
//binding.shareButton.setEnabled(false);
//binding.shareButton.setText(R.string.please_wait);
mHandler.removeCallbacks(mTickExecutor);
mHandler.postDelayed(() -> stopRecording(true), 500);
}
};
private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
@ -1336,8 +1428,8 @@ public class ConversationFragment extends XmppFragment
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
case ATTACHMENT_CHOICE_RECORD_VIDEO:
case ATTACHMENT_CHOICE_RECORD_VOICE:
case ATTACHMENT_CHOICE_CHOOSE_VIDEO:
case ATTACHMENT_CHOICE_RECORD_VOICE:
final Attachment.Type type = requestCode == ATTACHMENT_CHOICE_RECORD_VOICE ? Attachment.Type.RECORDING : Attachment.Type.FILE;
final List<Attachment> fileUris = Attachment.extractAttachments(getActivity(), data, type);
mediaPreviewAdapter.addMediaPreviews(fileUris);
@ -1619,6 +1711,12 @@ public class ConversationFragment extends XmppFragment
if (displayMetrics.heightPixels > 0) binding.textinput.setMaxHeight(displayMetrics.heightPixels / 4);
binding.textSendButton.setOnClickListener(this.mSendButtonListener);
if (binding.cancelButton != null) {
binding.cancelButton.setOnClickListener(this.mCancelVoiceRecord);
}
if (binding.shareButton != null) {
binding.shareButton.setOnClickListener(this.mShareVoiceRecord);
}
binding.contextPreviewCancel.setOnClickListener((v) -> {
setThread(null);
conversation.setUserSelectedThread(false);
@ -2984,8 +3082,7 @@ public class ConversationFragment extends XmppFragment
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
intent = new Intent(getActivity(), RecordingActivity.class);
intent.putExtra("ALTERNATIVE_CODEC", activity.xmppConnectionService.alternativeVoiceSettings());
recordVoice();
break;
case ATTACHMENT_CHOICE_LOCATION:
intent = GeoHelper.getFetchIntent(activity);
@ -3018,6 +3115,159 @@ public class ConversationFragment extends XmppFragment
}
}
public void recordVoice() {
this.binding.recordingVoiceActivity.setVisibility(View.VISIBLE);
if (!startRecording()) {
this.binding.shareButton.setEnabled(false);
this.binding.timer.setTextAppearance(activity, R.style.TextAppearance_Conversations_Title);
this.binding.timer.setText(R.string.unable_to_start_recording);
}
}
private boolean startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
if (activity.xmppConnectionService.getBooleanPreference("ALTERNATIVE_CODEC", R.bool.alternative_voice_settings)) {
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
} else {
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setAudioEncodingBitRate(96000);
mRecorder.setAudioSamplingRate(22050);
}
setupOutputFile();
mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
try {
mRecorder.prepare();
mRecorder.start();
recording = true;
mStartTime = SystemClock.elapsedRealtime();
mHandler.postDelayed(mTickExecutor, 100);
Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
return false;
}
}
protected void stopRecording() {
try {
mRecorder.stop();
mRecorder.release();
recording = false;
} catch (Exception e) {
e.printStackTrace();
}
}
protected void stopRecording(final boolean saveFile) {
try {
if (recording) {
stopRecording();
}
} catch (Exception e) {
if (saveFile) {
ToastCompat.makeText(activity, R.string.unable_to_save_recording, ToastCompat.LENGTH_SHORT).show();
return;
}
} finally {
mRecorder = null;
mStartTime = 0;
}
if (!saveFile && mOutputFile != null) {
if (mOutputFile.delete()) {
Log.d(Config.LOGTAG, "deleted canceled recording");
}
}
if (saveFile) {
new Thread(new Finisher(outputFileWrittenLatch, mOutputFile, activity)).start();
}
}
private class Finisher implements Runnable {
private final CountDownLatch latch;
private final File outputFile;
private final WeakReference<Activity> activityReference;
private Finisher(CountDownLatch latch, File outputFile, Activity activity) {
this.latch = latch;
this.outputFile = outputFile;
this.activityReference = new WeakReference<>(activity);
}
@Override
public void run() {
try {
if (!latch.await(8, TimeUnit.SECONDS)) {
Log.d(Config.LOGTAG, "time out waiting for output file to be written");
}
} catch (InterruptedException e) {
Log.d(Config.LOGTAG, "interrupted while waiting for output file to be written", e);
}
final Activity activity = activityReference.get();
if (activity == null) {
return;
}
activity.runOnUiThread(
() -> {
activity.setResult(
Activity.RESULT_OK, new Intent().setData(Uri.fromFile(outputFile)));
mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), Uri.fromFile(outputFile), Attachment.Type.RECORDING));
toggleInputMethod();
binding.recordingVoiceActivity.setVisibility(View.GONE);
//attachFileToConversation(conversation, Uri.fromFile(outputFile), "audio/mp4");
});
}
}
private static File generateOutputFilename(Context context) {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
return new File(getConversationsDirectory(context, SENT_AUDIOS)
+ dateFormat.format(new Date())
+ ".m4a");
}
private void setupOutputFile() {
mOutputFile = generateOutputFilename(activity);
final File parentDirectory = mOutputFile.getParentFile();
if (parentDirectory.mkdirs()) {
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
}
final File noMedia = new File(parentDirectory, ".nomedia");
if (!noMedia.exists()) {
try {
if (noMedia.createNewFile()) {
Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
}
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
}
}
setupFileObserver(parentDirectory);
}
private void setupFileObserver(final File directory) {
mFileObserver = new FileObserver(directory.getAbsolutePath()) {
@Override
public void onEvent(int event, String s) {
if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
outputFileWrittenLatch.countDown();
}
}
};
mFileObserver.startWatching();
}
private void tick() {
this.binding.timer.setText(TimeFrameUtils.formatTimePassed(mStartTime, true));
}
@Override
public void onResume() {
super.onResume();

View file

@ -115,6 +115,60 @@
android:dividerHeight="0dp"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/recordingVoiceActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:animateLayoutChanges="true" >
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/timer"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true">
<Button
android:id="@+id/cancel_button"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
android:textColor="?attr/colorAccent" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:textColor="?attr/colorAccent" />
<Button
android:id="@+id/share_button"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/attach"
android:textColor="?attr/colorAccent" />
</LinearLayout>
<TextView
android:id="@+id/timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:text="0:00.0"
android:textSize="30sp"
android:textStyle="bold"
android:typeface="monospace" />
</RelativeLayout>
<LinearLayout
android:id="@+id/context_preview"
android:visibility="gone"

View file

@ -2,9 +2,9 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/color_background_primary">
android:orientation="vertical">
<LinearLayout
android:id="@+id/button_bar"

View file

@ -115,6 +115,61 @@
android:dividerHeight="0dp"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/recordingVoiceActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:animateLayoutChanges="true" >
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/timer"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true">
<Button
android:id="@+id/cancel_button"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
android:textColor="?attr/colorAccent" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:textColor="?attr/colorAccent" />
<Button
android:id="@+id/share_button"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/attach"
android:textColor="?attr/colorAccent" />
</LinearLayout>
<TextView
android:id="@+id/timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:text="0:00.0"
android:textSize="30sp"
android:textStyle="bold"
android:typeface="monospace" />
</RelativeLayout>
<LinearLayout
android:id="@+id/context_preview"
android:visibility="gone"