/*
* 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;
}
}