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
17package com.android.soundrecorder;
18
19import java.io.File;
20import java.io.IOException;
21
22import android.content.Context;
23import android.media.AudioManager;
24import android.media.MediaPlayer;
25import android.media.MediaRecorder;
26import android.media.MediaPlayer.OnCompletionListener;
27import android.media.MediaPlayer.OnErrorListener;
28import android.os.Bundle;
29import android.os.Environment;
30import android.util.Log;
31
32public 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 {
49        public void onStateChanged(int state);
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
61    public Recorder() {
62    }
63
64    public void saveState(Bundle recorderState) {
65        recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath());
66        recorderState.putInt(SAMPLE_LENGTH_KEY, mSampleLength);
67    }
68
69    public int getMaxAmplitude() {
70        if (mState != RECORDING_STATE)
71            return 0;
72        return mRecorder.getMaxAmplitude();
73    }
74
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
97    public void setOnStateChangedListener(OnStateChangedListener listener) {
98        mOnStateChangedListener = listener;
99    }
100
101    public int state() {
102        return mState;
103    }
104
105    public int progress() {
106        if (mState == RECORDING_STATE || mState == PLAYING_STATE)
107            return (int) ((System.currentTimeMillis() - mSampleStart)/1000);
108        return 0;
109    }
110
111    public int sampleLength() {
112        return mSampleLength;
113    }
114
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     */
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     */
138    public void clear() {
139        stop();
140
141        mSampleLength = 0;
142
143        signalStateChanged(IDLE_STATE);
144    }
145
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
199    public void stopRecording() {
200        if (mRecorder == null)
201            return;
202
203        mRecorder.stop();
204        mRecorder.release();
205        mRecorder = null;
206
207        mSampleLength = (int)( (System.currentTimeMillis() - mSampleStart)/1000 );
208        setState(IDLE_STATE);
209    }
210
211    public void startPlayback() {
212        stop();
213
214        mPlayer = new MediaPlayer();
215        try {
216            mPlayer.setDataSource(mSampleFile.getAbsolutePath());
217            mPlayer.setOnCompletionListener(this);
218            mPlayer.setOnErrorListener(this);
219            mPlayer.prepare();
220            mPlayer.start();
221        } catch (IllegalArgumentException e) {
222            setError(INTERNAL_ERROR);
223            mPlayer = null;
224            return;
225        } catch (IOException e) {
226            setError(SDCARD_ACCESS_ERROR);
227            mPlayer = null;
228            return;
229        }
230
231        mSampleStart = System.currentTimeMillis();
232        setState(PLAYING_STATE);
233    }
234
235    public void stopPlayback() {
236        if (mPlayer == null) // we were not in playback
237            return;
238
239        mPlayer.stop();
240        mPlayer.release();
241        mPlayer = null;
242        setState(IDLE_STATE);
243    }
244
245    public void stop() {
246        stopRecording();
247        stopPlayback();
248    }
249
250    public boolean onError(MediaPlayer mp, int what, int extra) {
251        stop();
252        setError(SDCARD_ACCESS_ERROR);
253        return true;
254    }
255
256    public void onCompletion(MediaPlayer mp) {
257        stop();
258    }
259
260    private void setState(int state) {
261        if (state == mState)
262            return;
263
264        mState = state;
265        signalStateChanged(mState);
266    }
267
268    private void signalStateChanged(int state) {
269        if (mOnStateChangedListener != null)
270            mOnStateChangedListener.onStateChanged(state);
271    }
272
273    private void setError(int error) {
274        if (mOnStateChangedListener != null)
275            mOnStateChangedListener.onError(error);
276    }
277}
278