RecordingActivity.java

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