1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.soundrecorder;
18 
19 import java.io.File;
20 import java.io.IOException;
21 
22 import android.content.Context;
23 import android.media.AudioManager;
24 import android.media.MediaPlayer;
25 import android.media.MediaRecorder;
26 import android.media.MediaPlayer.OnCompletionListener;
27 import android.media.MediaPlayer.OnErrorListener;
28 import android.os.Bundle;
29 import android.os.Environment;
30 import android.util.Log;
31 
32 public class Recorder implements OnCompletionListener, OnErrorListener {
33     static final String SAMPLE_PREFIX = "recording";
34     static final String SAMPLE_PATH_KEY = "sample_path";
35     static final String SAMPLE_LENGTH_KEY = "sample_length";
36 
37     public static final int IDLE_STATE = 0;
38     public static final int RECORDING_STATE = 1;
39     public static final int PLAYING_STATE = 2;
40 
41     int mState = IDLE_STATE;
42 
43     public static final int NO_ERROR = 0;
44     public static final int SDCARD_ACCESS_ERROR = 1;
45     public static final int INTERNAL_ERROR = 2;
46     public static final int IN_CALL_RECORD_ERROR = 3;
47 
48     public interface OnStateChangedListener {
onStateChanged(int state)49         public void onStateChanged(int state);
onError(int error)50         public void onError(int error);
51     }
52     OnStateChangedListener mOnStateChangedListener = null;
53 
54     long mSampleStart = 0;       // time at which latest record or play operation started
55     int mSampleLength = 0;      // length of current sample
56     File mSampleFile = null;
57 
58     MediaRecorder mRecorder = null;
59     MediaPlayer mPlayer = null;
60 
Recorder()61     public Recorder() {
62     }
63 
saveState(Bundle recorderState)64     public void saveState(Bundle recorderState) {
65         recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath());
66         recorderState.putInt(SAMPLE_LENGTH_KEY, mSampleLength);
67     }
68 
getMaxAmplitude()69     public int getMaxAmplitude() {
70         if (mState != RECORDING_STATE)
71             return 0;
72         return mRecorder.getMaxAmplitude();
73     }
74 
restoreState(Bundle recorderState)75     public void restoreState(Bundle recorderState) {
76         String samplePath = recorderState.getString(SAMPLE_PATH_KEY);
77         if (samplePath == null)
78             return;
79         int sampleLength = recorderState.getInt(SAMPLE_LENGTH_KEY, -1);
80         if (sampleLength == -1)
81             return;
82 
83         File file = new File(samplePath);
84         if (!file.exists())
85             return;
86         if (mSampleFile != null
87                 && mSampleFile.getAbsolutePath().compareTo(file.getAbsolutePath()) == 0)
88             return;
89 
90         delete();
91         mSampleFile = file;
92         mSampleLength = sampleLength;
93 
94         signalStateChanged(IDLE_STATE);
95     }
96 
setOnStateChangedListener(OnStateChangedListener listener)97     public void setOnStateChangedListener(OnStateChangedListener listener) {
98         mOnStateChangedListener = listener;
99     }
100 
state()101     public int state() {
102         return mState;
103     }
104 
progress()105     public int progress() {
106         if (mState == RECORDING_STATE || mState == PLAYING_STATE)
107             return (int) ((System.currentTimeMillis() - mSampleStart)/1000);
108         return 0;
109     }
110 
sampleLength()111     public int sampleLength() {
112         return mSampleLength;
113     }
114 
sampleFile()115     public File sampleFile() {
116         return mSampleFile;
117     }
118 
119     /**
120      * Resets the recorder state. If a sample was recorded, the file is deleted.
121      */
delete()122     public void delete() {
123         stop();
124 
125         if (mSampleFile != null)
126             mSampleFile.delete();
127 
128         mSampleFile = null;
129         mSampleLength = 0;
130 
131         signalStateChanged(IDLE_STATE);
132     }
133 
134     /**
135      * Resets the recorder state. If a sample was recorded, the file is left on disk and will
136      * be reused for a new recording.
137      */
clear()138     public void clear() {
139         stop();
140 
141         mSampleLength = 0;
142 
143         signalStateChanged(IDLE_STATE);
144     }
145 
startRecording(int outputfileformat, String extension, Context context)146     public void startRecording(int outputfileformat, String extension, Context context) {
147         stop();
148 
149         if (mSampleFile == null) {
150             File sampleDir = Environment.getExternalStorageDirectory();
151             if (!sampleDir.canWrite()) // Workaround for broken sdcard support on the device.
152                 sampleDir = new File("/sdcard/sdcard");
153 
154             try {
155                 mSampleFile = File.createTempFile(SAMPLE_PREFIX, extension, sampleDir);
156             } catch (IOException e) {
157                 setError(SDCARD_ACCESS_ERROR);
158                 return;
159             }
160         }
161 
162         mRecorder = new MediaRecorder();
163         mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
164         mRecorder.setOutputFormat(outputfileformat);
165         mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
166         mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
167 
168         // Handle IOException
169         try {
170             mRecorder.prepare();
171         } catch(IOException exception) {
172             setError(INTERNAL_ERROR);
173             mRecorder.reset();
174             mRecorder.release();
175             mRecorder = null;
176             return;
177         }
178         // Handle RuntimeException if the recording couldn't start
179         try {
180             mRecorder.start();
181         } catch (RuntimeException exception) {
182             AudioManager audioMngr = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
183             boolean isInCall = ((audioMngr.getMode() == AudioManager.MODE_IN_CALL) ||
184                     (audioMngr.getMode() == AudioManager.MODE_IN_COMMUNICATION));
185             if (isInCall) {
186                 setError(IN_CALL_RECORD_ERROR);
187             } else {
188                 setError(INTERNAL_ERROR);
189             }
190             mRecorder.reset();
191             mRecorder.release();
192             mRecorder = null;
193             return;
194         }
195         mSampleStart = System.currentTimeMillis();
196         setState(RECORDING_STATE);
197     }
198 
stopRecording()199     public void stopRecording() {
200         if (mRecorder == null)
201             return;
202 
203         try {
204             mRecorder.stop();
205         } catch (RuntimeException e) {
206             Log.w("Recorder", "Catch RuntimeException on MediaRecorder.stop() due to a call " +
207                 "immediately after MediaRecorder.start().");
208         }
209         mRecorder.release();
210         mRecorder = null;
211 
212         mSampleLength = (int)( (System.currentTimeMillis() - mSampleStart)/1000 );
213         setState(IDLE_STATE);
214     }
215 
startPlayback()216     public void startPlayback() {
217         stop();
218 
219         mPlayer = new MediaPlayer();
220         try {
221             mPlayer.setDataSource(mSampleFile.getAbsolutePath());
222             mPlayer.setOnCompletionListener(this);
223             mPlayer.setOnErrorListener(this);
224             mPlayer.prepare();
225             mPlayer.start();
226         } catch (IllegalArgumentException e) {
227             setError(INTERNAL_ERROR);
228             mPlayer = null;
229             return;
230         } catch (IOException e) {
231             setError(SDCARD_ACCESS_ERROR);
232             mPlayer = null;
233             return;
234         }
235 
236         mSampleStart = System.currentTimeMillis();
237         setState(PLAYING_STATE);
238     }
239 
stopPlayback()240     public void stopPlayback() {
241         if (mPlayer == null) // we were not in playback
242             return;
243 
244         mPlayer.stop();
245         mPlayer.release();
246         mPlayer = null;
247         setState(IDLE_STATE);
248     }
249 
stop()250     public void stop() {
251         stopRecording();
252         stopPlayback();
253     }
254 
onError(MediaPlayer mp, int what, int extra)255     public boolean onError(MediaPlayer mp, int what, int extra) {
256         stop();
257         setError(SDCARD_ACCESS_ERROR);
258         return true;
259     }
260 
onCompletion(MediaPlayer mp)261     public void onCompletion(MediaPlayer mp) {
262         stop();
263     }
264 
setState(int state)265     private void setState(int state) {
266         if (state == mState)
267             return;
268 
269         mState = state;
270         signalStateChanged(mState);
271     }
272 
signalStateChanged(int state)273     private void signalStateChanged(int state) {
274         if (mOnStateChangedListener != null)
275             mOnStateChangedListener.onStateChanged(state);
276     }
277 
setError(int error)278     private void setError(int error) {
279         if (mOnStateChangedListener != null)
280             mOnStateChangedListener.onError(error);
281     }
282 }
283