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.os.Looper;
21import android.os.Handler;
22import android.os.Message;
23import android.media.MediaPlayer;
24import android.test.AndroidTestCase;
25import com.android.mediaframeworktest.MediaNames;
26
27/**
28 * A template class for running a method under test in all possible
29 * states of a MediaPlayer object.
30 *
31 * @see com.android.mediaframeworktest.unit.MediaPlayerSeekToStateUnitTest
32 * for an example of using this class.
33 *
34 * A typical concrete unit test class would implement the
35 * MediaPlayerMethodUnderTest interface and have a reference to an object of
36 * this class. Then it calls runTestOnMethod() to actually perform the unit
37 * tests.
38 *
39 */
40class MediaPlayerStateUnitTestTemplate extends AndroidTestCase {
41    private static final String TEST_PATH = MediaNames.TEST_PATH_1;
42    private static final String TAG = "MediaPlayerStateUnitTestTemplate";
43    private static final int SEEK_TO_END  = 135110;  // Milliseconds.
44    private static int WAIT_FOR_COMMAND_TO_COMPLETE = 1000;  // Milliseconds.
45
46    private MediaPlayerStateErrors mStateErrors = new MediaPlayerStateErrors();
47    private MediaPlayer mMediaPlayer = null;
48    private boolean mInitialized = false;
49    private boolean mOnCompletionHasBeenCalled = false;
50    private MediaPlayerStateErrors.MediaPlayerState mMediaPlayerState = null;
51    private Looper mLooper = null;
52    private final Object lock = new Object();
53    private MediaPlayerMethodUnderTest mMethodUnderTest = null;
54
55    // An Handler object is absolutely necessary for receiving callback
56    // messages from MediaPlayer objects.
57    private Handler mHandler = new Handler() {
58        @Override
59        public void handleMessage(Message msg) {
60            /*
61            switch(msg.what) {
62                case MediaPlayerStateErrors.MEDIA_PLAYER_ERROR:
63                    Log.v(TAG, "handleMessage: received MEDIA_PLAYER_ERROR message");
64                    break;
65                default:
66                    Log.v(TAG, "handleMessage: received unknown message");
67                break;
68            }
69            */
70        }
71    };
72
73    /**
74     * Runs the given method under test in all possible states of a MediaPlayer
75     * object.
76     *
77     * @param testMethod the method under test.
78     */
79    public void runTestOnMethod(MediaPlayerMethodUnderTest testMethod) {
80        mMethodUnderTest = testMethod;
81        if (mMethodUnderTest != null) {  // Method under test has been set?
82            initializeMessageLooper();
83            synchronized(lock) {
84                try {
85                    lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
86                } catch(Exception e) {
87                    Log.v(TAG, "runTestOnMethod: wait was interrupted.");
88                }
89            }
90            assertTrue(mInitialized);  // mMediaPlayer has been initialized?
91            checkMethodUnderTestInAllPossibleStates();
92            terminateMessageLooper();   // Release message looper thread.
93            assertTrue(mOnCompletionHasBeenCalled);
94            mMethodUnderTest.checkStateErrors(mStateErrors);
95            cleanUp();
96        }
97    }
98
99    /*
100     * Initializes the message looper so that the MediaPlayer object can
101     * receive the callback messages.
102     */
103    private void initializeMessageLooper() {
104        new Thread() {
105            @Override
106            public void run() {
107                // Set up a looper to be used by mMediaPlayer.
108                Looper.prepare();
109
110                // Save the looper so that we can terminate this thread
111                // after we are done with it.
112                mLooper = Looper.myLooper();
113
114                mMediaPlayer = new MediaPlayer();
115                mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
116                    public boolean onError(MediaPlayer player, int what, int extra) {
117                        Log.v(TAG, "onError has been called.");
118                        synchronized(lock) {
119                            Log.v(TAG, "notify lock.");
120                            setStateError(mMediaPlayerState, true);
121                            if (mMediaPlayerState != MediaPlayerStateErrors.MediaPlayerState.ERROR) {
122                                notifyStateError();
123                            }
124                            lock.notify();
125                        }
126                        return true;
127                    }
128                });
129                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
130                    public void onCompletion(MediaPlayer player) {
131                        Log.v(TAG, "onCompletion has been called.");
132                        synchronized(lock) {
133                            if (mMediaPlayerState == MediaPlayerStateErrors.MediaPlayerState.PLAYBACK_COMPLETED) {
134                                mOnCompletionHasBeenCalled = true;
135                            }
136                            lock.notify();
137                        }
138                    }
139                });
140                synchronized(lock) {
141                    mInitialized = true;
142                    lock.notify();
143                }
144                Looper.loop();  // Blocks forever until Looper.quit() is called.
145                Log.v(TAG, "initializeMessageLooper: quit.");
146            }
147        }.start();
148    }
149
150    /*
151     * Calls method under test in the given state of the MediaPlayer object.
152     *
153     * @param state the MediaPlayer state in which the method under test is called.
154     */
155    private void callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState state) {
156        Log.v(TAG, "call " + mMethodUnderTest + ": started in state " + state);
157        setMediaPlayerToState(state);
158        mMethodUnderTest.invokeMethodUnderTest(mMediaPlayer);
159        synchronized(lock) {
160            try {
161                lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
162           } catch(Exception e) {
163               Log.v(TAG, "callMediaPlayerMethodUnderTestInState: wait is interrupted in state " + state);
164           }
165        }
166        Log.v(TAG, "call " + mMethodUnderTest + ": ended in state " + state);
167    }
168
169    /*
170     * The following setMediaPlayerToXXXStateXXX methods sets the MediaPlayer
171     * object to the corresponding state, given the assumption that reset()
172     * always resets the MediaPlayer object to Idle (after reset) state.
173     */
174    private void setMediaPlayerToIdleStateAfterReset() {
175        try {
176            mMediaPlayer.reset();
177            mMediaPlayer.setDataSource(TEST_PATH);
178            mMediaPlayer.prepare();
179            mMediaPlayer.reset();
180        } catch(Exception e) {
181            Log.v(TAG, "setMediaPlayerToIdleStateAfterReset: Exception " + e.getClass().getName() + " was thrown.");
182            assertTrue(false);
183        }
184    }
185
186    private void setMediaPlayerToInitializedState() {
187        try {
188            mMediaPlayer.reset();
189            mMediaPlayer.setDataSource(TEST_PATH);
190        } catch(Exception e) {
191            Log.v(TAG, "setMediaPlayerToInitializedState: Exception " + e.getClass().getName() + " was thrown.");
192            assertTrue(false);
193        }
194    }
195
196    private void setMediaPlayerToPreparedState() {
197        try {
198            mMediaPlayer.reset();
199            mMediaPlayer.setDataSource(TEST_PATH);
200            mMediaPlayer.prepare();
201        } catch(Exception e) {
202            Log.v(TAG, "setMediaPlayerToPreparedState: Exception " + e.getClass().getName() + " was thrown.");
203            assertTrue(false);
204        }
205    }
206
207    private void setMediaPlayerToPreparedStateAfterStop() {
208        try {
209            mMediaPlayer.reset();
210            mMediaPlayer.setDataSource(TEST_PATH);
211            mMediaPlayer.prepare();
212            mMediaPlayer.start();
213            mMediaPlayer.stop();
214            mMediaPlayer.prepare();
215        } catch(Exception e) {
216            Log.v(TAG, "setMediaPlayerToPreparedStateAfterStop: Exception " + e.getClass().getName() + " was thrown.");
217            assertTrue(false);
218        }
219    }
220
221    private void setMediaPlayerToStartedState() {
222        try {
223            mMediaPlayer.reset();
224            mMediaPlayer.setDataSource(TEST_PATH);
225            mMediaPlayer.prepare();
226            mMediaPlayer.start();
227        } catch(Exception e) {
228            Log.v(TAG, "setMediaPlayerToStartedState: Exception " + e.getClass().getName() + " was thrown.");
229            assertTrue(false);
230        }
231    }
232
233    private void setMediaPlayerToStartedStateAfterPause() {
234        try {
235            mMediaPlayer.reset();
236            mMediaPlayer.setDataSource(TEST_PATH);
237            mMediaPlayer.prepare();
238            mMediaPlayer.start();
239            mMediaPlayer.pause();
240
241            // pause() is an asynchronous call and returns immediately, but
242            // PV player engine may take quite a while to actually set the
243            // player state to Paused; if we call start() right after pause()
244            // without waiting, start() may fail.
245            try {
246                Thread.sleep(MediaNames.PAUSE_WAIT_TIME);
247            } catch(Exception ie) {
248                Log.v(TAG, "sleep was interrupted and terminated prematurely");
249            }
250
251            mMediaPlayer.start();
252        } catch(Exception e) {
253            Log.v(TAG, "setMediaPlayerToStartedStateAfterPause: Exception " + e.getClass().getName() + " was thrown.");
254            assertTrue(false);
255        }
256    }
257
258    private void setMediaPlayerToPausedState() {
259        try {
260            mMediaPlayer.reset();
261            mMediaPlayer.setDataSource(TEST_PATH);
262            mMediaPlayer.prepare();
263            mMediaPlayer.start();
264            mMediaPlayer.pause();
265        } catch(Exception e) {
266            Log.v(TAG, "setMediaPlayerToPausedState: Exception " + e.getClass().getName() + " was thrown.");
267            assertTrue(false);
268        }
269    }
270
271    private void setMediaPlayerToStoppedState() {
272        try {
273            mMediaPlayer.reset();
274            mMediaPlayer.setDataSource(TEST_PATH);
275            mMediaPlayer.prepare();
276            mMediaPlayer.start();
277            mMediaPlayer.stop();
278        } catch(Exception e) {
279            Log.v(TAG, "setMediaPlayerToStoppedState: Exception " + e.getClass().getName() + " was thrown.");
280            assertTrue(false);
281        }
282    }
283
284    private void setMediaPlayerToPlaybackCompletedState() {
285        try {
286            mMediaPlayer.reset();
287            mMediaPlayer.setDataSource(TEST_PATH);
288            mMediaPlayer.prepare();
289            mMediaPlayer.seekTo(SEEK_TO_END);
290            mMediaPlayer.start();
291            synchronized(lock) {
292                try {
293                    lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
294                } catch(Exception e) {
295                    Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: wait was interrupted.");
296                }
297            }
298        } catch(Exception e) {
299            Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: Exception " + e.getClass().getName() + " was thrown.");
300            assertTrue(false);
301        }
302        Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: done.");
303    }
304
305    /*
306     * There are a lot of ways to force the MediaPlayer object to enter
307     * the Error state. The impact (such as onError is called or not) highly
308     * depends on how the Error state is entered.
309     */
310    private void setMediaPlayerToErrorState() {
311        try {
312            mMediaPlayer.reset();
313            mMediaPlayer.setDataSource(TEST_PATH);
314            mMediaPlayer.start();
315            synchronized(lock) {
316                try {
317                    lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
318                } catch(Exception e) {
319                    Log.v(TAG, "setMediaPlayerToErrorState: wait was interrupted.");
320                }
321            }
322        } catch(Exception e) {
323            Log.v(TAG, "setMediaPlayerToErrorState: Exception " + e.getClass().getName() + " was thrown.");
324            assertTrue(e instanceof IllegalStateException);
325        }
326        Log.v(TAG, "setMediaPlayerToErrorState: done.");
327    }
328
329    /*
330     * Sets the state of the MediaPlayer object to the specified one.
331     *
332     * @param state the state of the MediaPlayer object.
333     */
334    private void setMediaPlayerToState(MediaPlayerStateErrors.MediaPlayerState state) {
335        mMediaPlayerState = state;
336        switch(state) {
337            case IDLE:
338                // Does nothing.
339                break;
340            case IDLE_AFTER_RESET:
341                setMediaPlayerToIdleStateAfterReset();
342                break;
343            case INITIALIZED:
344                setMediaPlayerToInitializedState();
345                break;
346            case PREPARED:
347                setMediaPlayerToPreparedState();
348                break;
349            case PREPARED_AFTER_STOP:
350                setMediaPlayerToPreparedStateAfterStop();
351                break;
352            case STARTED:
353                setMediaPlayerToStartedState();
354                break;
355            case STARTED_AFTER_PAUSE:
356                setMediaPlayerToStartedStateAfterPause();
357                break;
358            case PAUSED:
359                setMediaPlayerToPausedState();
360                break;
361            case STOPPED:
362                setMediaPlayerToStoppedState();
363                break;
364            case PLAYBACK_COMPLETED:
365                setMediaPlayerToPlaybackCompletedState();
366                break;
367            case ERROR:
368                setMediaPlayerToErrorState();
369                break;
370        }
371    }
372
373    /*
374     * Sets the error value of the corresponding state to the given error.
375     *
376     * @param state the state of the MediaPlayer object.
377     * @param error the value of the state error to be set.
378     */
379    private void setStateError(MediaPlayerStateErrors.MediaPlayerState state, boolean error) {
380        switch(state) {
381            case IDLE:
382                mStateErrors.errorInIdleState = error;
383                break;
384            case IDLE_AFTER_RESET:
385                mStateErrors.errorInIdleStateAfterReset = error;
386                break;
387            case INITIALIZED:
388                mStateErrors.errorInInitializedState = error;
389                break;
390            case PREPARED:
391                mStateErrors.errorInPreparedState = error;
392                break;
393            case PREPARED_AFTER_STOP:
394                mStateErrors.errorInPreparedStateAfterStop = error;
395                break;
396            case STARTED:
397                mStateErrors.errorInStartedState = error;
398                break;
399            case STARTED_AFTER_PAUSE:
400                mStateErrors.errorInStartedStateAfterPause = error;
401                break;
402            case PAUSED:
403                mStateErrors.errorInPausedState = error;
404                break;
405            case STOPPED:
406                mStateErrors.errorInStoppedState = error;
407                break;
408            case PLAYBACK_COMPLETED:
409                mStateErrors.errorInPlaybackCompletedState = error;
410                break;
411            case ERROR:
412                mStateErrors.errorInErrorState = error;
413                break;
414        }
415    }
416
417    private void notifyStateError() {
418        mHandler.sendMessage(mHandler.obtainMessage(MediaPlayerStateErrors.MEDIA_PLAYER_ERROR));
419    }
420
421    private void checkIdleState() {
422        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.IDLE);
423    }
424
425    private void checkIdleStateAfterReset() {
426        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.IDLE_AFTER_RESET);
427    }
428
429    private void checkInitializedState() {
430        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.INITIALIZED);
431    }
432
433    private void checkPreparedState() {
434        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PREPARED);
435    }
436
437    private void checkPreparedStateAfterStop() {
438        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PREPARED_AFTER_STOP);
439    }
440
441    private void checkStartedState() {
442        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STARTED);
443    }
444
445    private void checkPausedState() {
446        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PAUSED);
447    }
448
449    private void checkStartedStateAfterPause() {
450        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STARTED_AFTER_PAUSE);
451    }
452
453    private void checkStoppedState() {
454        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STOPPED);
455    }
456
457    private void checkPlaybackCompletedState() {
458        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PLAYBACK_COMPLETED);
459    }
460
461    private void checkErrorState() {
462        callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.ERROR);
463    }
464
465    /*
466     * Checks the given method under test in all possible states of the MediaPlayer object.
467     */
468    private void checkMethodUnderTestInAllPossibleStates() {
469        // Must be called first.
470        checkIdleState();
471
472        // The sequence of the following method calls should not
473        // affect the test results.
474        checkErrorState();
475        checkIdleStateAfterReset();
476        checkInitializedState();
477        checkStartedState();
478        checkStartedStateAfterPause();
479        checkPausedState();
480        checkPreparedState();
481
482        checkPreparedStateAfterStop();
483
484        checkPlaybackCompletedState();
485        checkStoppedState();
486    }
487
488    /*
489     * Terminates the message looper thread.
490     */
491    private void terminateMessageLooper() {
492        mLooper.quit();
493        mMediaPlayer.release();
494    }
495
496    /*
497     * Cleans up all the internal object references.
498     */
499    private void cleanUp() {
500        mMediaPlayer = null;
501        mMediaPlayerState = null;
502        mLooper = null;
503        mStateErrors = null;
504        mMethodUnderTest = null;
505    }
506}
507