/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mediaframeworktest.unit; import android.util.Log; import android.os.Looper; import android.os.Handler; import android.os.Message; import android.media.MediaPlayer; import android.test.AndroidTestCase; import com.android.mediaframeworktest.MediaNames; /** * A template class for running a method under test in all possible * states of a MediaPlayer object. * * @see com.android.mediaframeworktest.unit.MediaPlayerSeekToStateUnitTest * for an example of using this class. * * A typical concrete unit test class would implement the * MediaPlayerMethodUnderTest interface and have a reference to an object of * this class. Then it calls runTestOnMethod() to actually perform the unit * tests. * */ class MediaPlayerStateUnitTestTemplate extends AndroidTestCase { private static final String TEST_PATH = MediaNames.TEST_PATH_1; private static final String TAG = "MediaPlayerStateUnitTestTemplate"; private static final int SEEK_TO_END = 135110; // Milliseconds. private static int WAIT_FOR_COMMAND_TO_COMPLETE = 1000; // Milliseconds. private MediaPlayerStateErrors mStateErrors = new MediaPlayerStateErrors(); private MediaPlayer mMediaPlayer = null; private boolean mInitialized = false; private boolean mOnCompletionHasBeenCalled = false; private MediaPlayerStateErrors.MediaPlayerState mMediaPlayerState = null; private Looper mLooper = null; private final Object lock = new Object(); private MediaPlayerMethodUnderTest mMethodUnderTest = null; // An Handler object is absolutely necessary for receiving callback // messages from MediaPlayer objects. private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { /* switch(msg.what) { case MediaPlayerStateErrors.MEDIA_PLAYER_ERROR: Log.v(TAG, "handleMessage: received MEDIA_PLAYER_ERROR message"); break; default: Log.v(TAG, "handleMessage: received unknown message"); break; } */ } }; /** * Runs the given method under test in all possible states of a MediaPlayer * object. * * @param testMethod the method under test. */ public void runTestOnMethod(MediaPlayerMethodUnderTest testMethod) { mMethodUnderTest = testMethod; if (mMethodUnderTest != null) { // Method under test has been set? initializeMessageLooper(); synchronized(lock) { try { lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE); } catch(Exception e) { Log.v(TAG, "runTestOnMethod: wait was interrupted."); } } assertTrue(mInitialized); // mMediaPlayer has been initialized? checkMethodUnderTestInAllPossibleStates(); terminateMessageLooper(); // Release message looper thread. assertTrue(mOnCompletionHasBeenCalled); mMethodUnderTest.checkStateErrors(mStateErrors); cleanUp(); } } /* * Initializes the message looper so that the MediaPlayer object can * receive the callback messages. */ private void initializeMessageLooper() { new Thread() { @Override public void run() { // Set up a looper to be used by mMediaPlayer. Looper.prepare(); // Save the looper so that we can terminate this thread // after we are done with it. mLooper = Looper.myLooper(); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer player, int what, int extra) { Log.v(TAG, "onError has been called."); synchronized(lock) { Log.v(TAG, "notify lock."); setStateError(mMediaPlayerState, true); if (mMediaPlayerState != MediaPlayerStateErrors.MediaPlayerState.ERROR) { notifyStateError(); } lock.notify(); } return true; } }); mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer player) { Log.v(TAG, "onCompletion has been called."); synchronized(lock) { if (mMediaPlayerState == MediaPlayerStateErrors.MediaPlayerState.PLAYBACK_COMPLETED) { mOnCompletionHasBeenCalled = true; } lock.notify(); } } }); synchronized(lock) { mInitialized = true; lock.notify(); } Looper.loop(); // Blocks forever until Looper.quit() is called. Log.v(TAG, "initializeMessageLooper: quit."); } }.start(); } /* * Calls method under test in the given state of the MediaPlayer object. * * @param state the MediaPlayer state in which the method under test is called. */ private void callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState state) { Log.v(TAG, "call " + mMethodUnderTest + ": started in state " + state); setMediaPlayerToState(state); mMethodUnderTest.invokeMethodUnderTest(mMediaPlayer); synchronized(lock) { try { lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE); } catch(Exception e) { Log.v(TAG, "callMediaPlayerMethodUnderTestInState: wait is interrupted in state " + state); } } Log.v(TAG, "call " + mMethodUnderTest + ": ended in state " + state); } /* * The following setMediaPlayerToXXXStateXXX methods sets the MediaPlayer * object to the corresponding state, given the assumption that reset() * always resets the MediaPlayer object to Idle (after reset) state. */ private void setMediaPlayerToIdleStateAfterReset() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.reset(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToIdleStateAfterReset: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToInitializedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToInitializedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToPreparedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToPreparedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToPreparedStateAfterStop() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.start(); mMediaPlayer.stop(); mMediaPlayer.prepare(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToPreparedStateAfterStop: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToStartedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.start(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToStartedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToStartedStateAfterPause() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.start(); mMediaPlayer.pause(); // pause() is an asynchronous call and returns immediately, but // PV player engine may take quite a while to actually set the // player state to Paused; if we call start() right after pause() // without waiting, start() may fail. try { Thread.sleep(MediaNames.PAUSE_WAIT_TIME); } catch(Exception ie) { Log.v(TAG, "sleep was interrupted and terminated prematurely"); } mMediaPlayer.start(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToStartedStateAfterPause: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToPausedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.start(); mMediaPlayer.pause(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToPausedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToStoppedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.start(); mMediaPlayer.stop(); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToStoppedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } } private void setMediaPlayerToPlaybackCompletedState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.prepare(); mMediaPlayer.seekTo(SEEK_TO_END); mMediaPlayer.start(); synchronized(lock) { try { lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: wait was interrupted."); } } } catch(Exception e) { Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(false); } Log.v(TAG, "setMediaPlayerToPlaybackCompletedState: done."); } /* * There are a lot of ways to force the MediaPlayer object to enter * the Error state. The impact (such as onError is called or not) highly * depends on how the Error state is entered. */ private void setMediaPlayerToErrorState() { try { mMediaPlayer.reset(); mMediaPlayer.setDataSource(TEST_PATH); mMediaPlayer.start(); synchronized(lock) { try { lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE); } catch(Exception e) { Log.v(TAG, "setMediaPlayerToErrorState: wait was interrupted."); } } } catch(Exception e) { Log.v(TAG, "setMediaPlayerToErrorState: Exception " + e.getClass().getName() + " was thrown."); assertTrue(e instanceof IllegalStateException); } Log.v(TAG, "setMediaPlayerToErrorState: done."); } /* * Sets the state of the MediaPlayer object to the specified one. * * @param state the state of the MediaPlayer object. */ private void setMediaPlayerToState(MediaPlayerStateErrors.MediaPlayerState state) { mMediaPlayerState = state; switch(state) { case IDLE: // Does nothing. break; case IDLE_AFTER_RESET: setMediaPlayerToIdleStateAfterReset(); break; case INITIALIZED: setMediaPlayerToInitializedState(); break; case PREPARED: setMediaPlayerToPreparedState(); break; case PREPARED_AFTER_STOP: setMediaPlayerToPreparedStateAfterStop(); break; case STARTED: setMediaPlayerToStartedState(); break; case STARTED_AFTER_PAUSE: setMediaPlayerToStartedStateAfterPause(); break; case PAUSED: setMediaPlayerToPausedState(); break; case STOPPED: setMediaPlayerToStoppedState(); break; case PLAYBACK_COMPLETED: setMediaPlayerToPlaybackCompletedState(); break; case ERROR: setMediaPlayerToErrorState(); break; } } /* * Sets the error value of the corresponding state to the given error. * * @param state the state of the MediaPlayer object. * @param error the value of the state error to be set. */ private void setStateError(MediaPlayerStateErrors.MediaPlayerState state, boolean error) { switch(state) { case IDLE: mStateErrors.errorInIdleState = error; break; case IDLE_AFTER_RESET: mStateErrors.errorInIdleStateAfterReset = error; break; case INITIALIZED: mStateErrors.errorInInitializedState = error; break; case PREPARED: mStateErrors.errorInPreparedState = error; break; case PREPARED_AFTER_STOP: mStateErrors.errorInPreparedStateAfterStop = error; break; case STARTED: mStateErrors.errorInStartedState = error; break; case STARTED_AFTER_PAUSE: mStateErrors.errorInStartedStateAfterPause = error; break; case PAUSED: mStateErrors.errorInPausedState = error; break; case STOPPED: mStateErrors.errorInStoppedState = error; break; case PLAYBACK_COMPLETED: mStateErrors.errorInPlaybackCompletedState = error; break; case ERROR: mStateErrors.errorInErrorState = error; break; } } private void notifyStateError() { mHandler.sendMessage(mHandler.obtainMessage(MediaPlayerStateErrors.MEDIA_PLAYER_ERROR)); } private void checkIdleState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.IDLE); } private void checkIdleStateAfterReset() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.IDLE_AFTER_RESET); } private void checkInitializedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.INITIALIZED); } private void checkPreparedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PREPARED); } private void checkPreparedStateAfterStop() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PREPARED_AFTER_STOP); } private void checkStartedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STARTED); } private void checkPausedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PAUSED); } private void checkStartedStateAfterPause() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STARTED_AFTER_PAUSE); } private void checkStoppedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.STOPPED); } private void checkPlaybackCompletedState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.PLAYBACK_COMPLETED); } private void checkErrorState() { callMediaPlayerMethodUnderTestInState(MediaPlayerStateErrors.MediaPlayerState.ERROR); } /* * Checks the given method under test in all possible states of the MediaPlayer object. */ private void checkMethodUnderTestInAllPossibleStates() { // Must be called first. checkIdleState(); // The sequence of the following method calls should not // affect the test results. checkErrorState(); checkIdleStateAfterReset(); checkInitializedState(); checkStartedState(); checkStartedStateAfterPause(); checkPausedState(); checkPreparedState(); checkPreparedStateAfterStop(); checkPlaybackCompletedState(); checkStoppedState(); } /* * Terminates the message looper thread. */ private void terminateMessageLooper() { mLooper.quit(); mMediaPlayer.release(); } /* * Cleans up all the internal object references. */ private void cleanUp() { mMediaPlayer = null; mMediaPlayerState = null; mLooper = null; mStateErrors = null; mMethodUnderTest = null; } }