integrate voice recorder
This commit is contained in:
parent
cfaebbdbd1
commit
2f974c3eb2
|
@ -35,7 +35,6 @@
|
||||||
privacy
|
privacy
|
||||||
* Rely on existing, well established protocols (XMPP)
|
* Rely on existing, well established protocols (XMPP)
|
||||||
* Do not require a Google Account or specifically Google Cloud Messaging (GCM)
|
* Do not require a Google Account or specifically Google Cloud Messaging (GCM)
|
||||||
* Require as few permissions as possible
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<uses-feature android:name="android.hardware.location.network" android:required="false" />
|
<uses-feature android:name="android.hardware.location.network" android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.READ_PHONE_STATE"
|
android:name="android.permission.READ_PHONE_STATE"
|
||||||
|
@ -58,12 +59,11 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ShareLocationActivity"
|
android:name=".ui.ShareLocationActivity"
|
||||||
android:label="@string/title_activity_share_location" >
|
android:label="@string/title_activity_share_location"/>
|
||||||
<intent-filter>
|
<activity
|
||||||
<action android:name="eu.siacs.conversations.location.request" />
|
android:name=".ui.RecordingActivity"
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
android:theme="@style/ConversationsTheme.Dialog"
|
||||||
</intent-filter>
|
android:configChanges="orientation|screenSize"/>
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ShowLocationActivity"
|
android:name=".ui.ShowLocationActivity"
|
||||||
android:label="@string/title_activity_show_location" >
|
android:label="@string/title_activity_show_location" >
|
||||||
|
|
|
@ -55,6 +55,7 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.ui.RecordingActivity;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.ExifHelper;
|
import eu.siacs.conversations.utils.ExifHelper;
|
||||||
import eu.siacs.conversations.utils.FileUtils;
|
import eu.siacs.conversations.utils.FileUtils;
|
||||||
|
@ -76,11 +77,13 @@ public class FileBackend {
|
||||||
this.mXmppConnectionService = service;
|
this.mXmppConnectionService = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNoMedia() {
|
private void createNoMedia(File diretory) {
|
||||||
final File nomedia = new File(getConversationsDirectory("Files") + ".nomedia");
|
final File noMedia = new File(diretory,".nomedia");
|
||||||
if (!nomedia.exists()) {
|
if (!noMedia.exists()) {
|
||||||
try {
|
try {
|
||||||
nomedia.createNewFile();
|
if (!noMedia.createNewFile()) {
|
||||||
|
Log.d(Config.LOGTAG,"created nomedia file "+noMedia.getAbsolutePath());
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG, "could not create nomedia file");
|
Log.d(Config.LOGTAG, "could not create nomedia file");
|
||||||
}
|
}
|
||||||
|
@ -88,16 +91,25 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMediaScanner(File file) {
|
public void updateMediaScanner(File file) {
|
||||||
String path = file.getAbsolutePath();
|
if (!isInDirectoryThatShouldNotBeScanned(mXmppConnectionService, file)) {
|
||||||
if (!path.startsWith(getConversationsDirectory("Files"))) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
intent.setData(Uri.fromFile(file));
|
intent.setData(Uri.fromFile(file));
|
||||||
mXmppConnectionService.sendBroadcast(intent);
|
mXmppConnectionService.sendBroadcast(intent);
|
||||||
} else {
|
} else if (file.getAbsolutePath().startsWith(getAppMediaDirectory(mXmppConnectionService))) {
|
||||||
createNoMedia();
|
createNoMedia(file.getParentFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isInDirectoryThatShouldNotBeScanned(Context context, File file) {
|
||||||
|
String path = file.getAbsolutePath();
|
||||||
|
for(String type : new String[]{RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
|
||||||
|
if (path.startsWith(getConversationsDirectory(context, type))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean deleteFile(Message message) {
|
public boolean deleteFile(Message message) {
|
||||||
File file = getFile(message);
|
File file = getFile(message);
|
||||||
if (file.delete()) {
|
if (file.delete()) {
|
||||||
|
@ -186,13 +198,21 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getConversationsDirectory(final String type) {
|
public String getConversationsDirectory(final String type) {
|
||||||
|
return getConversationsDirectory(mXmppConnectionService, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getConversationsDirectory(Context context, final String type) {
|
||||||
if (Config.ONLY_INTERNAL_STORAGE) {
|
if (Config.ONLY_INTERNAL_STORAGE) {
|
||||||
return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/" + type + "/";
|
return context.getFilesDir().getAbsolutePath() + "/" + type + "/";
|
||||||
} else {
|
} else {
|
||||||
return Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations " + type + "/";
|
return getAppMediaDirectory(context)+context.getString(R.string.app_name)+" " + type + "/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAppMediaDirectory(Context context) {
|
||||||
|
return Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+context.getString(R.string.app_name)+"/Media/";
|
||||||
|
}
|
||||||
|
|
||||||
public static String getConversationsLogsDirectory() {
|
public static String getConversationsLogsDirectory() {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
|
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,7 +279,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasStoragePermission(REQUEST_ADD_EDITOR_CONTENT)) {
|
if (hasPermissions(REQUEST_ADD_EDITOR_CONTENT, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
attachImageToConversation(inputContentInfo.getContentUri());
|
attachImageToConversation(inputContentInfo.getContentUri());
|
||||||
} else {
|
} else {
|
||||||
mPendingEditorContent = inputContentInfo.getContentUri();
|
mPendingEditorContent = inputContentInfo.getContentUri();
|
||||||
|
@ -1284,12 +1284,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attachFile(final int attachmentChoice) {
|
public void attachFile(final int attachmentChoice) {
|
||||||
if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
|
if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
|
||||||
if (!hasStorageAndCameraPermission(attachmentChoice)) {
|
if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
|
||||||
|
if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
|
} else if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
|
||||||
if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(attachmentChoice)) {
|
if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1365,7 +1369,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@StringRes int res;
|
@StringRes int res;
|
||||||
if (Manifest.permission.CAMERA.equals(getFirstDenied(grantResults, permissions))) {
|
String firstDenied = getFirstDenied(grantResults, permissions);
|
||||||
|
if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
|
||||||
|
res = R.string.no_microphone_permission;
|
||||||
|
} else if (Manifest.permission.CAMERA.equals(firstDenied)) {
|
||||||
res = R.string.no_camera_permission;
|
res = R.string.no_camera_permission;
|
||||||
} else {
|
} else {
|
||||||
res = R.string.no_storage_permission;
|
res = R.string.no_storage_permission;
|
||||||
|
@ -1375,7 +1382,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startDownloadable(Message message) {
|
public void startDownloadable(Message message) {
|
||||||
if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(REQUEST_START_DOWNLOAD)) {
|
if (!hasPermissions(REQUEST_START_DOWNLOAD, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
this.mPendingDownloadableMessage = message;
|
this.mPendingDownloadableMessage = message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1443,27 +1450,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasStoragePermission(int requestCode) {
|
private boolean hasPermissions(int requestCode, String... permissions) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
final List<String> missingPermissions = new ArrayList<>();
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
|
for(String permission : permissions) {
|
||||||
return false;
|
if (Config.ONLY_INTERNAL_STORAGE && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
} else {
|
continue;
|
||||||
return true;
|
}
|
||||||
}
|
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
} else {
|
missingPermissions.add(permission);
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasStorageAndCameraPermission(int requestCode) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
List<String> missingPermissions = new ArrayList<>();
|
|
||||||
if (!Config.ONLY_INTERNAL_STORAGE && activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
missingPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
||||||
}
|
|
||||||
if (activity.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
missingPermissions.add(Manifest.permission.CAMERA);
|
|
||||||
}
|
}
|
||||||
if (missingPermissions.size() == 0) {
|
if (missingPermissions.size() == 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1489,7 +1485,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
final PresenceSelector.OnPresenceSelected callback = () -> {
|
final PresenceSelector.OnPresenceSelected callback = () -> {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
boolean chooser = false;
|
boolean chooser = false;
|
||||||
String fallbackPackageId = null;
|
|
||||||
switch (attachmentChoice) {
|
switch (attachmentChoice) {
|
||||||
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
||||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
@ -1515,12 +1510,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
break;
|
break;
|
||||||
case ATTACHMENT_CHOICE_RECORD_VOICE:
|
case ATTACHMENT_CHOICE_RECORD_VOICE:
|
||||||
intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
|
intent = new Intent(getActivity(), RecordingActivity.class);
|
||||||
fallbackPackageId = "eu.siacs.conversations.voicerecorder";
|
|
||||||
break;
|
break;
|
||||||
case ATTACHMENT_CHOICE_LOCATION:
|
case ATTACHMENT_CHOICE_LOCATION:
|
||||||
intent.setAction("eu.siacs.conversations.location.request");
|
intent = new Intent(getActivity(), ShareLocationActivity.class);
|
||||||
fallbackPackageId = "eu.siacs.conversations.sharelocation";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
|
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
|
||||||
|
@ -1531,8 +1524,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
} else {
|
} else {
|
||||||
startActivityForResult(intent, attachmentChoice);
|
startActivityForResult(intent, attachmentChoice);
|
||||||
}
|
}
|
||||||
} else if (fallbackPackageId != null) {
|
|
||||||
startActivity(getInstallApkIntent(fallbackPackageId));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) {
|
if (account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) {
|
||||||
|
@ -1543,28 +1534,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getInstallApkIntent(final String packageId) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse("market://details?id=" + packageId));
|
|
||||||
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
|
|
||||||
return intent;
|
|
||||||
} else {
|
|
||||||
intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId));
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
new Handler().post(() -> {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
if (activity == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final PackageManager packageManager = activity.getPackageManager();
|
|
||||||
ConversationMenuConfigurator.updateAttachmentAvailability(packageManager);
|
|
||||||
getActivity().invalidateOptionsMenu();
|
|
||||||
});
|
|
||||||
super.onResume();
|
super.onResume();
|
||||||
binding.messagesView.post(this::fireReadEvent);
|
binding.messagesView.post(this::fireReadEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.FileObserver;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
|
|
||||||
|
public class RecordingActivity extends Activity implements View.OnClickListener {
|
||||||
|
|
||||||
|
public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings";
|
||||||
|
|
||||||
|
private TextView mTimerTextView;
|
||||||
|
private Button mCancelButton;
|
||||||
|
private Button mStopButton;
|
||||||
|
|
||||||
|
private MediaRecorder mRecorder;
|
||||||
|
private long mStartTime = 0;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler();
|
||||||
|
private Runnable mTickExecutor = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
tick();
|
||||||
|
mHandler.postDelayed(mTickExecutor, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private File mOutputFile;
|
||||||
|
private boolean mShouldFinishAfterWrite = false;
|
||||||
|
|
||||||
|
private FileObserver mFileObserver;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
setTheme(ThemeHelper.findDialog(this));
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_recording);
|
||||||
|
this.mTimerTextView = (TextView) this.findViewById(R.id.timer);
|
||||||
|
this.mCancelButton = (Button) this.findViewById(R.id.cancel_button);
|
||||||
|
this.mCancelButton.setOnClickListener(this);
|
||||||
|
this.mStopButton = (Button) this.findViewById(R.id.share_button);
|
||||||
|
this.mStopButton.setOnClickListener(this);
|
||||||
|
this.setFinishOnTouchOutside(false);
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if (!startRecording()) {
|
||||||
|
mStopButton.setEnabled(false);
|
||||||
|
Toast.makeText(this, R.string.unable_to_start_recording, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (mRecorder != null) {
|
||||||
|
mHandler.removeCallbacks(mTickExecutor);
|
||||||
|
stopRecording(false);
|
||||||
|
}
|
||||||
|
if (mFileObserver != null) {
|
||||||
|
mFileObserver.stopWatching();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean startRecording() {
|
||||||
|
mRecorder = new MediaRecorder();
|
||||||
|
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||||
|
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||||
|
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
|
||||||
|
mRecorder.setAudioEncodingBitRate(48000);
|
||||||
|
mRecorder.setAudioSamplingRate(16000);
|
||||||
|
setupOutputFile();
|
||||||
|
mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
|
||||||
|
|
||||||
|
try {
|
||||||
|
mRecorder.prepare();
|
||||||
|
mRecorder.start();
|
||||||
|
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(boolean saveFile) {
|
||||||
|
mShouldFinishAfterWrite = saveFile;
|
||||||
|
mRecorder.stop();
|
||||||
|
mRecorder.release();
|
||||||
|
mRecorder = null;
|
||||||
|
mStartTime = 0;
|
||||||
|
if (!saveFile && mOutputFile != null) {
|
||||||
|
if (mOutputFile.delete()) {
|
||||||
|
Log.d(Config.LOGTAG,"deleted canceled recording");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File generateOutputFilename(Context context) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
||||||
|
String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
|
||||||
|
return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupOutputFile() {
|
||||||
|
mOutputFile = generateOutputFilename(this);
|
||||||
|
File parentDirectory = mOutputFile.getParentFile();
|
||||||
|
if (parentDirectory.mkdirs()) {
|
||||||
|
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
|
||||||
|
}
|
||||||
|
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(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) {
|
||||||
|
if (mShouldFinishAfterWrite) {
|
||||||
|
setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mFileObserver.startWatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tick() {
|
||||||
|
long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
|
||||||
|
int minutes = (int) (time / 60000);
|
||||||
|
int seconds = (int) (time / 1000) % 60;
|
||||||
|
int milliseconds = (int) (time / 100) % 10;
|
||||||
|
mTimerTextView.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
switch (view.getId()) {
|
||||||
|
case R.id.cancel_button:
|
||||||
|
mHandler.removeCallbacks(mTickExecutor);
|
||||||
|
stopRecording(false);
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
break;
|
||||||
|
case R.id.share_button:
|
||||||
|
mStopButton.setEnabled(false);
|
||||||
|
mStopButton.setText(R.string.please_wait);
|
||||||
|
mHandler.removeCallbacks(mTickExecutor);
|
||||||
|
mHandler.postDelayed(() -> stopRecording(true), 500);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,8 +45,8 @@ public class AttachmentTool {
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
return uris;
|
return uris;
|
||||||
}
|
}
|
||||||
Uri uri = intent.getData();
|
final Uri uri = intent.getData();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) {
|
if (uri == null) {
|
||||||
final ClipData clipData = intent.getClipData();
|
final ClipData clipData = intent.getClipData();
|
||||||
if (clipData != null) {
|
if (clipData != null) {
|
||||||
for (int i = 0; i < clipData.getItemCount(); ++i) {
|
for (int i = 0; i < clipData.getItemCount(); ++i) {
|
||||||
|
|
|
@ -45,13 +45,7 @@ import eu.siacs.conversations.entities.Message;
|
||||||
|
|
||||||
public class ConversationMenuConfigurator {
|
public class ConversationMenuConfigurator {
|
||||||
|
|
||||||
private static boolean showSoundRecorderAttachment = false;
|
|
||||||
private static boolean showLocationAttachment = false;
|
|
||||||
|
|
||||||
|
|
||||||
public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu) {
|
public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu) {
|
||||||
final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice);
|
|
||||||
final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location);
|
|
||||||
final MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
|
final MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
|
||||||
|
|
||||||
final boolean visible;
|
final boolean visible;
|
||||||
|
@ -66,9 +60,6 @@ public class ConversationMenuConfigurator {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
menuAttachLocation.setVisible(showLocationAttachment);
|
|
||||||
menuAttachSoundRecorder.setVisible(showSoundRecorderAttachment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu) {
|
public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu) {
|
||||||
|
@ -118,9 +109,4 @@ public class ConversationMenuConfigurator {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateAttachmentAvailability(PackageManager packageManager) {
|
|
||||||
showSoundRecorderAttachment = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(packageManager) != null;
|
|
||||||
showLocationAttachment = new Intent("eu.siacs.conversations.location.request").resolveActivity(packageManager) != null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,21 @@ public class ThemeHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int findDialog(Context context) {
|
||||||
|
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final Resources resources = context.getResources();
|
||||||
|
final boolean dark = sharedPreferences.getString(SettingsActivity.THEME, resources.getString(R.string.theme)).equals("dark");
|
||||||
|
final String fontSize = sharedPreferences.getString("font_size", resources.getString(R.string.default_font_size));
|
||||||
|
switch (fontSize) {
|
||||||
|
case "medium":
|
||||||
|
return dark ? R.style.ConversationsTheme_Dark_Dialog_Medium : R.style.ConversationsTheme_Dialog_Medium;
|
||||||
|
case "large":
|
||||||
|
return dark ? R.style.ConversationsTheme_Dark_Dialog_Large : R.style.ConversationsTheme_Dialog_Large;
|
||||||
|
default:
|
||||||
|
return dark ? R.style.ConversationsTheme_Dark_Dialog : R.style.ConversationsTheme_Dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isDark(@StyleRes int id) {
|
public static boolean isDark(@StyleRes int id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.style.ConversationsTheme_Dark:
|
case R.style.ConversationsTheme_Dark:
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/color_background_primary">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/button_bar"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_below="@+id/timer">
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_marginBottom="7dp"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:background="?attr/divider"/>
|
||||||
|
|
||||||
|
<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/share"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/timer"
|
||||||
|
android:textAppearance="@style/TextAppearance.Conversations.Display2.Monospace"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="0:00.0"/>
|
||||||
|
</RelativeLayout>
|
|
@ -6,6 +6,7 @@
|
||||||
<attr name="TextSizeSubhead" format="dimension" />
|
<attr name="TextSizeSubhead" format="dimension" />
|
||||||
<attr name="TextSizeBody1" format="dimension" />
|
<attr name="TextSizeBody1" format="dimension" />
|
||||||
<attr name="TextSizeBody2" format="dimension" />
|
<attr name="TextSizeBody2" format="dimension" />
|
||||||
|
<attr name="TextSizeDisplay2" format="dimension" />
|
||||||
<attr name="TextSizeInput" format="dimension" />
|
<attr name="TextSizeInput" format="dimension" />
|
||||||
<attr name="TextSeparation" format="dimension"/>
|
<attr name="TextSeparation" format="dimension"/>
|
||||||
|
|
||||||
|
|
|
@ -699,4 +699,7 @@
|
||||||
<string name="title_activity_share_location">Share location</string>
|
<string name="title_activity_share_location">Share location</string>
|
||||||
<string name="title_activity_show_location">Show location</string>
|
<string name="title_activity_show_location">Show location</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
|
<string name="unable_to_start_recording">Unable to start recording</string>
|
||||||
|
<string name="please_wait">Please wait…</string>
|
||||||
|
<string name="no_microphone_permission">Conversations needs access to the microphone</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<style name="TextAppearance.Conversations.Display2.Monospace" parent="TextAppearance.AppCompat.Display2">
|
||||||
|
<item name="android:textSize">?TextSizeDisplay2</item>
|
||||||
|
<item name="android:fontFamily" tools:targetApi="jelly_bean">monospace</item>
|
||||||
|
<item name="android:typeface">monospace</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Conversations.Title" parent="TextAppearance.AppCompat.Title">
|
<style name="TextAppearance.Conversations.Title" parent="TextAppearance.AppCompat.Title">
|
||||||
<item name="android:textSize">?TextSizeTitle</item>
|
<item name="android:textSize">?TextSizeTitle</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -239,4 +239,47 @@
|
||||||
<item name="colorPrimaryDark">@color/grey300</item>
|
<item name="colorPrimaryDark">@color/grey300</item>
|
||||||
<item name="android:windowBackground">@drawable/background</item>
|
<item name="android:windowBackground">@drawable/background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dialog" parent="@style/Theme.AppCompat.Light.Dialog">
|
||||||
|
<item name="colorPrimary">@color/primary500</item>
|
||||||
|
<item name="colorPrimaryDark">@color/primary700</item>
|
||||||
|
<item name="colorAccent">@color/accent</item>
|
||||||
|
<item name="color_background_primary">@color/grey50</item>
|
||||||
|
<item name="divider">@color/black12</item>
|
||||||
|
<item name="TextSizeBody2">14sp</item>
|
||||||
|
<item name="TextSizeDisplay2">45sp</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dark.Dialog" parent="@style/Theme.AppCompat.Dialog">
|
||||||
|
<item name="colorPrimary">@color/primary500</item>
|
||||||
|
<item name="colorPrimaryDark">@color/primary700</item>
|
||||||
|
<item name="colorAccent">@color/accent</item>
|
||||||
|
<item name="color_background_primary">@color/grey800</item>
|
||||||
|
<item name="divider">@color/white12</item>
|
||||||
|
<item name="TextSizeBody2">14sp</item>
|
||||||
|
<item name="TextSizeDisplay2">45sp</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dialog.Medium" parent="ConversationsTheme.Dialog">
|
||||||
|
<item name="TextSizeBody2">16sp</item>
|
||||||
|
<item name="TextSizeDisplay2">51sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dark.Dialog.Medium" parent="ConversationsTheme.Dark.Dialog">
|
||||||
|
<item name="TextSizeBody2">16sp</item>
|
||||||
|
<item name="TextSizeDisplay2">51sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dialog.Large" parent="ConversationsTheme.Dialog">
|
||||||
|
<item name="TextSizeBody2">18sp</item>
|
||||||
|
<item name="TextSizeDisplay2">56sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.Dark.Dialog.Large" parent="ConversationsTheme.Dark.Dialog">
|
||||||
|
<item name="TextSizeBody2">18sp</item>
|
||||||
|
<item name="TextSizeTitle">56sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue