1/*
2 * Copyright (C) 2008 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.mediaframeworktest.unit;
18
19import android.util.Log;
20import android.media.MediaRecorder;
21import android.test.AndroidTestCase;
22
23/**
24 * A template class for running a method under test in all possible
25 * states of a MediaRecorder object.
26 *
27 * @see com.android.mediaframeworktest.unit.MediaRecorderStopStateUnitTest
28 * for an example of using this class.
29 *
30 * A typical concrete unit test class would implement the
31 * MediaRecorderMethodUnderTest interface and have a reference to an object of
32 * this class. Then it calls runTestOnMethod() to actually perform the unit
33 * tests. It is recommended that the toString() method of the concrete unit test
34 * class be overridden to use the actual method name under test for logging
35 * purpose.
36 *
37 */
38class MediaRecorderStateUnitTestTemplate extends AndroidTestCase {
39    public static final String RECORD_OUTPUT_PATH = "/sdcard/recording.3gp";
40    public static final int OUTPUT_FORMAT= MediaRecorder.OutputFormat.THREE_GPP;
41    public static final int AUDIO_ENCODER = MediaRecorder.AudioEncoder.AMR_NB;
42    public static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
43    private static final String TAG = "MediaRecorderStateUnitTest";
44    private MediaRecorderStateErrors mStateErrors = new MediaRecorderStateErrors();
45    private MediaRecorder mMediaRecorder = new MediaRecorder();
46    private MediaRecorderStateErrors.MediaRecorderState mMediaRecorderState = null;
47    private MediaRecorderMethodUnderTest mMethodUnderTest = null;
48
49    /**
50     * Runs the given method under test in all possible states of a MediaRecorder
51     * object.
52     *
53     * @param testMethod the method under test.
54     */
55    public void runTestOnMethod(MediaRecorderMethodUnderTest testMethod) {
56        mMethodUnderTest = testMethod;
57        if (mMethodUnderTest != null) {  // Method under test has been set?
58            checkMethodUnderTestInAllPossibleStates();
59            mMethodUnderTest.checkStateErrors(mStateErrors);
60            cleanUp();
61        }
62    }
63
64    /*
65     * Calls method under test in the given state of the MediaRecorder object.
66     *
67     * @param state the MediaRecorder state in which the method under test is called.
68     */
69    private void callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState state) {
70        Log.v(TAG, "call " + mMethodUnderTest + ": started in state " + state);
71        setMediaRecorderToState(state);
72        try {
73            mMethodUnderTest.invokeMethodUnderTest(mMediaRecorder);
74        } catch(Exception e) {
75            setStateError(mMediaRecorderState, true);
76        }
77        Log.v(TAG, "call " + mMethodUnderTest + ": ended in state " + state);
78    }
79
80    /*
81     * The following setMediaRecorderToXXXStateXXX methods sets the MediaRecorder
82     * object to the corresponding state, given the assumption that reset()
83     * always resets the MediaRecorder object to Initial (after reset) state.
84     */
85    private void setMediaRecorderToInitialStateAfterReset() {
86        try {
87            mMediaRecorder.reset();
88        } catch(Exception e) {
89            fail("setMediaRecorderToInitialStateAfterReset: Exception " + e.getClass().getName() + " was thrown.");
90        }
91    }
92
93    // FIXME:
94    // In the past, stop() == reset().
95    // However, this is no longer true. The plan is to have a STOPPED state.
96    // and from STOPPED state, start can be called without the need to
97    // do the recording configuration again.
98    private void setMediaRecorderToInitialStateAfterStop() {
99        try {
100            mMediaRecorder.reset();
101/*
102            mMediaRecorder.setAudioSource(AUDIO_SOURCE);
103            mMediaRecorder.setOutputFormat(OUTPUT_FORMAT);
104            mMediaRecorder.setAudioEncoder(AUDIO_ENCODER);
105            mMediaRecorder.setOutputFile(RECORD_OUTPUT_PATH);
106            mMediaRecorder.prepare();
107            mMediaRecorder.start();
108            mMediaRecorder.stop();
109*/
110        } catch(Exception e) {
111            fail("setMediaRecorderToInitialStateAfterReset: Exception " + e.getClass().getName() + " was thrown.");
112        }
113    }
114
115    private void setMediaRecorderToInitializedState() {
116        try {
117            mMediaRecorder.reset();
118            if (mMethodUnderTest.toString() != "setAudioSource()") {
119                mMediaRecorder.setAudioSource(AUDIO_SOURCE);
120            }
121        } catch(Exception e) {
122            fail("setMediaRecorderToInitializedState: Exception " + e.getClass().getName() + " was thrown.");
123        }
124    }
125
126    private void setMediaRecorderToPreparedState() {
127        try {
128            mMediaRecorder.reset();
129            mMediaRecorder.setAudioSource(AUDIO_SOURCE);
130            mMediaRecorder.setOutputFormat(OUTPUT_FORMAT);
131            mMediaRecorder.setAudioEncoder(AUDIO_ENCODER);
132            mMediaRecorder.setOutputFile(RECORD_OUTPUT_PATH);
133            mMediaRecorder.prepare();
134        } catch(Exception e) {
135            fail("setMediaRecorderToPreparedState: Exception " + e.getClass().getName() + " was thrown.");
136        }
137    }
138
139    private void setMediaRecorderToRecordingState() {
140        try {
141            mMediaRecorder.reset();
142            mMediaRecorder.setAudioSource(AUDIO_SOURCE);
143            mMediaRecorder.setOutputFormat(OUTPUT_FORMAT);
144            mMediaRecorder.setAudioEncoder(AUDIO_ENCODER);
145            mMediaRecorder.setOutputFile(RECORD_OUTPUT_PATH);
146            mMediaRecorder.prepare();
147            mMediaRecorder.start();
148        } catch(Exception e) {
149            fail("setMediaRecorderToRecordingState: Exception " + e.getClass().getName() + " was thrown.");
150        }
151    }
152
153    private void setMediaRecorderToDataSourceConfiguredState() {
154        try {
155            mMediaRecorder.reset();
156            mMediaRecorder.setAudioSource(AUDIO_SOURCE);
157            mMediaRecorder.setOutputFormat(OUTPUT_FORMAT);
158
159            /* Skip setAudioEncoder() and setOutputFile() calls if
160             * the method under test is setAudioEncoder() since this
161             * method can only be called once even in the DATASOURCECONFIGURED state
162             */
163            if (mMethodUnderTest.toString() != "setAudioEncoder()") {
164                mMediaRecorder.setAudioEncoder(AUDIO_ENCODER);
165            }
166
167            if (mMethodUnderTest.toString() != "setOutputFile()") {
168                mMediaRecorder.setOutputFile(RECORD_OUTPUT_PATH);
169            }
170        } catch(Exception e) {
171            fail("setMediaRecorderToDataSourceConfiguredState: Exception " + e.getClass().getName() + " was thrown.");
172        }
173    }
174
175    /*
176     * There are a lot of ways to force the MediaRecorder object to enter
177     * the Error state. We arbitrary choose one here.
178     */
179    private void setMediaRecorderToErrorState() {
180        try {
181            mMediaRecorder.reset();
182
183            /* Skip setAudioSource() if the method under test is setAudioEncoder()
184             * Because, otherwise, it is valid to call setAudioEncoder() after
185             * start() since start() will fail, and then the mMediaRecorder
186             * won't be set to the Error state
187             */
188            if (mMethodUnderTest.toString() != "setAudioEncoder()") {
189                mMediaRecorder.setAudioSource(AUDIO_SOURCE);
190            }
191
192            /* Skip setOutputFormat if the method under test is setOutputFile()
193             *  Because, otherwise, it is valid to call setOutputFile() after
194             * start() since start() will fail, and then the mMediaRecorder
195             * won't be set to the Error state
196             */
197            if (mMethodUnderTest.toString() != "setOutputFile()") {
198                mMediaRecorder.setOutputFormat(OUTPUT_FORMAT);
199            }
200
201            mMediaRecorder.start();
202        } catch(Exception e) {
203            if (!(e instanceof IllegalStateException)) {
204                fail("setMediaRecorderToErrorState: Exception " + e.getClass().getName() + " was thrown.");
205            }
206        }
207        Log.v(TAG, "setMediaRecorderToErrorState: done.");
208    }
209
210    /*
211     * Sets the state of the MediaRecorder object to the specified one.
212     *
213     * @param state the state of the MediaRecorder object.
214     */
215    private void setMediaRecorderToState(MediaRecorderStateErrors.MediaRecorderState state) {
216        mMediaRecorderState = state;
217        switch(state) {
218            case INITIAL:
219                // Does nothing.
220                break;
221            case INITIAL_AFTER_RESET:
222                setMediaRecorderToInitialStateAfterReset();
223                break;
224            case INITIAL_AFTER_STOP:
225                setMediaRecorderToInitialStateAfterStop();
226                break;
227            case INITIALIZED:
228                setMediaRecorderToInitializedState();
229                break;
230            case DATASOURCECONFIGURED:
231                setMediaRecorderToDataSourceConfiguredState();
232                break;
233            case PREPARED:
234                setMediaRecorderToPreparedState();
235                break;
236            case RECORDING:
237                setMediaRecorderToRecordingState();
238                break;
239            case ERROR:
240                setMediaRecorderToErrorState();
241                break;
242        }
243    }
244
245    /*
246     * Sets the error value of the corresponding state to the given error.
247     *
248     * @param state the state of the MediaRecorder object.
249     * @param error the value of the state error to be set.
250     */
251    private void setStateError(MediaRecorderStateErrors.MediaRecorderState state, boolean error) {
252        switch(state) {
253            case INITIAL:
254                mStateErrors.errorInInitialState = error;
255                break;
256            case INITIAL_AFTER_RESET:
257                mStateErrors.errorInInitialStateAfterReset = error;
258                break;
259            case INITIAL_AFTER_STOP:
260                mStateErrors.errorInInitialStateAfterStop = error;
261                break;
262            case INITIALIZED:
263                mStateErrors.errorInInitializedState = error;
264                break;
265            case DATASOURCECONFIGURED:
266                mStateErrors.errorInDataSourceConfiguredState = error;
267                break;
268            case PREPARED:
269                mStateErrors.errorInPreparedState = error;
270                break;
271            case RECORDING:
272                mStateErrors.errorInRecordingState = error;
273                break;
274            case ERROR:
275                mStateErrors.errorInErrorState = error;
276                break;
277        }
278    }
279
280    private void checkInitialState() {
281        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.INITIAL);
282    }
283
284    private void checkInitialStateAfterReset() {
285        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.INITIAL_AFTER_RESET);
286    }
287
288    private void checkInitialStateAfterStop() {
289        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.INITIAL_AFTER_STOP);
290    }
291
292    private void checkInitializedState() {
293        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.INITIALIZED);
294    }
295
296    private void checkPreparedState() {
297        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.PREPARED);
298    }
299
300    private void checkRecordingState() {
301        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.RECORDING);
302    }
303
304    private void checkDataSourceConfiguredState() {
305        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.DATASOURCECONFIGURED);
306    }
307
308    private void checkErrorState() {
309        callMediaRecorderMethodUnderTestInState(MediaRecorderStateErrors.MediaRecorderState.ERROR);
310    }
311
312    /*
313     * Checks the given method under test in all possible states of the MediaRecorder object.
314     */
315    private void checkMethodUnderTestInAllPossibleStates() {
316        // Must be called first.
317        checkInitialState();
318
319        // The sequence of the following method calls should not
320        // affect the test results.
321        checkErrorState();
322        checkInitialStateAfterReset();
323        checkInitialStateAfterStop();
324        checkInitializedState();
325        checkRecordingState();
326        checkDataSourceConfiguredState();
327        checkPreparedState();
328    }
329
330    /*
331     * Cleans up all the internal object references.
332     */
333    private void cleanUp() {
334        mMediaRecorder.release();
335        mMediaRecorder = null;
336        mMediaRecorderState = null;
337        mStateErrors = null;
338        mMethodUnderTest = null;
339    }
340}
341