RecordingActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.annotation.SuppressLint;
  4import android.app.Activity;
  5import android.content.Context;
  6import android.content.Intent;
  7import android.databinding.DataBindingUtil;
  8import android.media.MediaRecorder;
  9import android.net.Uri;
 10import android.os.Bundle;
 11import android.os.FileObserver;
 12import android.os.Handler;
 13import android.os.SystemClock;
 14import android.util.Log;
 15import android.view.View;
 16import android.view.WindowManager;
 17import android.widget.Toast;
 18
 19import java.io.File;
 20import java.io.IOException;
 21import java.text.SimpleDateFormat;
 22import java.util.Date;
 23import java.util.Locale;
 24
 25import eu.siacs.conversations.Config;
 26import eu.siacs.conversations.R;
 27import eu.siacs.conversations.databinding.ActivityRecordingBinding;
 28import eu.siacs.conversations.persistance.FileBackend;
 29import eu.siacs.conversations.utils.ThemeHelper;
 30
 31public class RecordingActivity extends Activity implements View.OnClickListener {
 32
 33	public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings";
 34
 35	private ActivityRecordingBinding binding;
 36
 37	private MediaRecorder mRecorder;
 38	private long mStartTime = 0;
 39
 40	private Handler mHandler = new Handler();
 41	private Runnable mTickExecutor = new Runnable() {
 42		@Override
 43		public void run() {
 44			tick();
 45			mHandler.postDelayed(mTickExecutor, 100);
 46		}
 47	};
 48
 49	private File mOutputFile;
 50	private boolean mShouldFinishAfterWrite = false;
 51
 52	private FileObserver mFileObserver;
 53
 54	@Override
 55	protected void onCreate(Bundle savedInstanceState) {
 56		setTheme(ThemeHelper.findDialog(this));
 57		super.onCreate(savedInstanceState);
 58		this.binding = DataBindingUtil.setContentView(this,R.layout.activity_recording);
 59		this.binding.cancelButton.setOnClickListener(this);
 60		this.binding.shareButton.setOnClickListener(this);
 61		this.setFinishOnTouchOutside(false);
 62		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 63	}
 64
 65	@Override
 66	protected void onStart() {
 67		super.onStart();
 68		if (!startRecording()) {
 69			this.binding.shareButton.setEnabled(false);
 70			this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title);
 71			this.binding.timer.setText(R.string.unable_to_start_recording);
 72		}
 73	}
 74
 75	@Override
 76	protected void onStop() {
 77		super.onStop();
 78		if (mRecorder != null) {
 79			mHandler.removeCallbacks(mTickExecutor);
 80			stopRecording(false);
 81		}
 82		if (mFileObserver != null) {
 83			mFileObserver.stopWatching();
 84		}
 85	}
 86
 87	private boolean startRecording() {
 88		mRecorder = new MediaRecorder();
 89		mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 90		mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
 91		mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
 92		mRecorder.setAudioEncodingBitRate(96000);
 93		mRecorder.setAudioSamplingRate(22050);
 94		setupOutputFile();
 95		mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
 96
 97		try {
 98			mRecorder.prepare();
 99			mRecorder.start();
100			mStartTime = SystemClock.elapsedRealtime();
101			mHandler.postDelayed(mTickExecutor, 100);
102			Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
103			return true;
104		} catch (Exception e) {
105			Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
106			return false;
107		}
108	}
109
110	protected void stopRecording(boolean saveFile) {
111		mShouldFinishAfterWrite = saveFile;
112		try {
113			mRecorder.stop();
114			mRecorder.release();
115		} catch (Exception e) {
116			if (saveFile) {
117				Toast.makeText(this,R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show();
118			}
119		} finally {
120			mRecorder = null;
121			mStartTime = 0;
122		}
123		if (!saveFile && mOutputFile != null) {
124			if (mOutputFile.delete()) {
125				Log.d(Config.LOGTAG,"deleted canceled recording");
126			}
127		}
128	}
129
130	private static File generateOutputFilename(Context context) {
131		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
132		String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
133		return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
134	}
135
136	private void setupOutputFile() {
137		mOutputFile = generateOutputFilename(this);
138		File parentDirectory = mOutputFile.getParentFile();
139		if (parentDirectory.mkdirs()) {
140			Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
141		}
142		File noMedia = new File(parentDirectory, ".nomedia");
143		if (!noMedia.exists()) {
144			try {
145				if (noMedia.createNewFile()) {
146					Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
147				}
148			} catch (IOException e) {
149				Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
150			}
151		}
152		setupFileObserver(parentDirectory);
153	}
154
155	private void setupFileObserver(File directory) {
156		mFileObserver = new FileObserver(directory.getAbsolutePath()) {
157			@Override
158			public void onEvent(int event, String s) {
159				if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
160					if (mShouldFinishAfterWrite) {
161						setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
162						finish();
163					}
164				}
165			}
166		};
167		mFileObserver.startWatching();
168	}
169
170	@SuppressLint("SetTextI18n")
171	private void tick() {
172		long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
173		int minutes = (int) (time / 60000);
174		int seconds = (int) (time / 1000) % 60;
175		int milliseconds = (int) (time / 100) % 10;
176		this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
177	}
178
179	@Override
180	public void onClick(View view) {
181		switch (view.getId()) {
182			case R.id.cancel_button:
183				mHandler.removeCallbacks(mTickExecutor);
184				stopRecording(false);
185				setResult(RESULT_CANCELED);
186				finish();
187				break;
188			case R.id.share_button:
189				this.binding.shareButton.setEnabled(false);
190				this.binding.shareButton.setText(R.string.please_wait);
191				mHandler.removeCallbacks(mTickExecutor);
192				mHandler.postDelayed(() -> stopRecording(true), 500);
193				break;
194		}
195	}
196}