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			Toast.makeText(this, R.string.unable_to_start_recording, Toast.LENGTH_SHORT).show();
 71		}
 72	}
 73
 74	@Override
 75	protected void onStop() {
 76		super.onStop();
 77		if (mRecorder != null) {
 78			mHandler.removeCallbacks(mTickExecutor);
 79			stopRecording(false);
 80		}
 81		if (mFileObserver != null) {
 82			mFileObserver.stopWatching();
 83		}
 84	}
 85
 86	private boolean startRecording() {
 87		mRecorder = new MediaRecorder();
 88		mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 89		mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
 90		mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
 91		mRecorder.setAudioEncodingBitRate(96000);
 92		mRecorder.setAudioSamplingRate(22050);
 93		setupOutputFile();
 94		mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
 95
 96		try {
 97			mRecorder.prepare();
 98			mRecorder.start();
 99			mStartTime = SystemClock.elapsedRealtime();
100			mHandler.postDelayed(mTickExecutor, 100);
101			Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
102			return true;
103		} catch (Exception e) {
104			Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
105			return false;
106		}
107	}
108
109	protected void stopRecording(boolean saveFile) {
110		mShouldFinishAfterWrite = saveFile;
111		mRecorder.stop();
112		mRecorder.release();
113		mRecorder = null;
114		mStartTime = 0;
115		if (!saveFile && mOutputFile != null) {
116			if (mOutputFile.delete()) {
117				Log.d(Config.LOGTAG,"deleted canceled recording");
118			}
119		}
120	}
121
122	private static File generateOutputFilename(Context context) {
123		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
124		String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
125		return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
126	}
127
128	private void setupOutputFile() {
129		mOutputFile = generateOutputFilename(this);
130		File parentDirectory = mOutputFile.getParentFile();
131		if (parentDirectory.mkdirs()) {
132			Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
133		}
134		File noMedia = new File(parentDirectory, ".nomedia");
135		if (!noMedia.exists()) {
136			try {
137				if (noMedia.createNewFile()) {
138					Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
139				}
140			} catch (IOException e) {
141				Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
142			}
143		}
144		setupFileObserver(parentDirectory);
145	}
146
147	private void setupFileObserver(File directory) {
148		mFileObserver = new FileObserver(directory.getAbsolutePath()) {
149			@Override
150			public void onEvent(int event, String s) {
151				if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
152					if (mShouldFinishAfterWrite) {
153						setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
154						finish();
155					}
156				}
157			}
158		};
159		mFileObserver.startWatching();
160	}
161
162	@SuppressLint("SetTextI18n")
163	private void tick() {
164		long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
165		int minutes = (int) (time / 60000);
166		int seconds = (int) (time / 1000) % 60;
167		int milliseconds = (int) (time / 100) % 10;
168		this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
169	}
170
171	@Override
172	public void onClick(View view) {
173		switch (view.getId()) {
174			case R.id.cancel_button:
175				mHandler.removeCallbacks(mTickExecutor);
176				stopRecording(false);
177				setResult(RESULT_CANCELED);
178				finish();
179				break;
180			case R.id.share_button:
181				this.binding.shareButton.setEnabled(false);
182				this.binding.shareButton.setText(R.string.please_wait);
183				mHandler.removeCallbacks(mTickExecutor);
184				mHandler.postDelayed(() -> stopRecording(true), 500);
185				break;
186		}
187	}
188}