/* * Copyright (C) 2014 Haruki Hasegawa * * 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.h6ah4i.android.media.openslmediaplayer.base; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; import com.h6ah4i.android.media.IBasicMediaPlayer; import com.h6ah4i.android.media.IMediaPlayerFactory; import com.h6ah4i.android.media.IReleasable; import com.h6ah4i.android.media.standard.StandardMediaPlayer; import com.h6ah4i.android.media.standard.StandardMediaPlayerFactory; import com.h6ah4i.android.media.openslmediaplayer.testing.ParameterizedInstrumentationTestCase; import com.h6ah4i.android.media.openslmediaplayer.testing.ParameterizedTestArgs; import com.h6ah4i.android.media.openslmediaplayer.testing.ParameterizedTestSuiteBuilder; import com.h6ah4i.android.media.openslmediaplayer.utils.CommonTestCaseUtils; import com.h6ah4i.android.media.openslmediaplayer.utils.CompletionListenerObject; import com.h6ah4i.android.media.openslmediaplayer.utils.DebugCompat; import com.h6ah4i.android.media.openslmediaplayer.utils.ErrorListenerObject; import com.h6ah4i.android.media.openslmediaplayer.utils.PreparedListenerObject; import com.h6ah4i.android.media.openslmediaplayer.utils.ThrowableRunnable; import junit.framework.TestCase; import junit.framework.TestSuite; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.util.ArrayList; import java.util.List; public abstract class BasicMediaPlayerTestCaseBase extends ParameterizedInstrumentationTestCase implements TestObjectBaseWrapper.Host { protected static final int SHORT_EVENT_WAIT_DURATION = 250; protected static final int DEFAULT_EVENT_WAIT_DURATION = 5000; protected static final int DURATION_LIMIT = 60000; protected static final String TESTSOUND_DIR = "testsounds"; // Local asset files protected static final String LOCAL_SHORT_SILENT_STEREO_MP3 = TESTSOUND_DIR + "/short_silent.mp3"; protected static final String LOCAL_440HZ_STEREO_MP3 = TESTSOUND_DIR + "/440hz_stereo.mp3"; protected static final String LOCAL_880HZ_STEREO_MP3 = TESTSOUND_DIR + "/880hz_stereo.mp3"; protected static final String LOCAL_440HZ_STEREO_OGG = TESTSOUND_DIR + "/440hz_stereo.ogg"; protected static final String LOCAL_880HZ_STEREO_OGG = TESTSOUND_DIR + "/880hz_stereo.ogg"; protected static final String LOCAL_440HZ_MONO_MP3 = TESTSOUND_DIR + "/440hz_monaural.mp3"; protected static final String LOCAL_880HZ_MONO_MP3 = TESTSOUND_DIR + "/880hz_monaural.mp3"; protected static final String LOCAL_440HZ_MONO_OGG = TESTSOUND_DIR + "/440hz_monaural.ogg"; protected static final String LOCAL_880HZ_MONO_OGG = TESTSOUND_DIR + "/880hz_monaural.ogg"; protected static final String LOCAL_440HZ_STEREO_48K_MP3 = TESTSOUND_DIR + "/440hz_stereo_48k.mp3"; // Online files protected static final Uri ONLINE_URI_3MIN_WHITENOISE_WAV = Uri.parse("http://" + GlobalTestOptions.TEST_SERVER_ROOT + "/media/whitenoise_stereo_3min.wav"); protected static final Uri ONLINE_URI_440HZ_STEREO_MP3 = Uri.parse("http://" + GlobalTestOptions.TEST_SERVER_ROOT + "/media/440hz_stereo.mp3"); // Player object states protected enum PlayerState { Idle, Initialized, Preparing, Prepared, Started, Paused, Stopped, PlaybackCompleted, ErrorBeforePrepared, ErrorAfterPrepared, End } // fields private final String mTestClassName; private IMediaPlayerFactory mFactory; protected static TestSuite buildBasicTestSuite( Class<? extends BasicMediaPlayerTestCaseBase> clazz, Class<? extends IMediaPlayerFactory> factoryClazz) { List<BasicTestParams> params = new ArrayList<BasicTestParams>(); params.add(new BasicTestParams(factoryClazz)); return ParameterizedTestSuiteBuilder.build(clazz, params); } protected static class BasicTestParams { private final Class<? extends IMediaPlayerFactory> mFactoryClass; public BasicTestParams(Class<? extends IMediaPlayerFactory> factoryClass) { mFactoryClass = factoryClass; } public IMediaPlayerFactory createFactory(Context context) { try { return mFactoryClass.getConstructor(Context.class).newInstance(context); } catch (Exception e) { throw new IllegalStateException(e); } } @Override public String toString() { return mFactoryClass.getSimpleName(); } } public BasicMediaPlayerTestCaseBase() { super(); mTestClassName = ((Object) this).getClass().getSimpleName(); } public BasicMediaPlayerTestCaseBase(ParameterizedTestArgs args) { super(args); mTestClassName = ((Object) this).getClass().getSimpleName(); } protected IMediaPlayerFactory getFactory() { return mFactory; } // // setUp and tearDown // @Override protected void setUp() throws Exception { memoryCheckStart(); super.setUp(); copyAllTestSoundsToStorage(); mFactory = onCreateFactory(); } @Override protected void tearDown() throws Exception { releaseQuietly(mFactory); mFactory = null; super.tearDown(); memoryCheckEnd(); } // // Memory leak checker // long mUsedMemorySize1; private void memoryCheckStart() { System.gc(); System.runFinalization(); System.gc(); mUsedMemorySize1 = DebugCompat.getPss(); } private void memoryCheckEnd() { System.gc(); System.runFinalization(); System.gc(); final long used1 = mUsedMemorySize1; final long used2 = DebugCompat.getPss(); final long diff = used2 - used1; final String infoText = " (increased: " + diff + " KiB, current: " + used2 + " KiB, @" + mTestClassName + "." + getName() + ")"; if (diff >= 1000) { Log.e("RAM Info", "Huge memory leak detected" + infoText); } else if (diff >= 500) { Log.e("RAM Info", "Large memory leak detected" + infoText); } else if (diff >= 50) { Log.w("RAM Info", "Memory leak detected" + infoText); } else if (diff >= 5) { Log.w("RAM Info", "Small memory leak detected" + infoText); } else { Log.v("RAM Info", "No memory leak detected" + infoText); } } protected Context getContext() { return getInstrumentation().getTargetContext(); } protected File getTempDir() { File dir = getContext().getCacheDir(); // if (dir != null) { // return dir; // } dir = getContext().getExternalCacheDir(); if (dir != null) { return dir; } dir = getContext().getFilesDir(); return dir; } protected static void closeQuietly(AssetFileDescriptor afd) { CommonTestCaseUtils.closeQuietly(afd); } protected static void closeQuietly(InputStream is) { CommonTestCaseUtils.closeQuietly(is); } protected static void releaseQuietly(IReleasable releasable) { CommonTestCaseUtils.releaseQuietly(releasable); } protected static int safeGetDuration(IBasicMediaPlayer player, int limit) { limit = Math.max(0, limit); if (player == null) return 0; // NOTE: The getDuration() function may returns // unpredictable value when the player object is in illegal state int value = player.getDuration(); if (value < 0 || value > limit) { value = 0; } return value; } protected IMediaPlayerFactory onCreateFactory() { if (getTestParams() instanceof BasicTestParams) { return ((BasicTestParams) getTestParams()).createFactory(getContext()); } throw new IllegalStateException("Must override this method"); } protected void copyAllTestSoundsToStorage() throws IOException { String[] assets = null; AssetManager am = getContext().getAssets(); File destBaseDir = getTempDir(); assets = am.list(TESTSOUND_DIR); for (String asset : assets) { CommonTestCaseUtils.copyAssetFile( am, TESTSOUND_DIR + File.separator + asset, destBaseDir, false); } } protected String getStorageFilePath(String name) { return new File(getTempDir().getAbsolutePath(), name).getAbsolutePath(); } protected void failNotOverrided() { fail("Subclass must override this method"); } protected static void assertRange(int expectedMin, int expectedMax, int actual) { if (actual < expectedMin) { fail("expectedMin: <" + expectedMin + ">, actual: <" + actual + ">"); } if (actual > expectedMax) { fail("expectedMax: <" + expectedMax + ">, actual: <" + actual + ">"); } } protected static void assertLargerThanOrEqual(int expectedMin, int actual) { if (actual < expectedMin) { fail("expectedMin: <" + expectedMin + ">, actual: <" + actual + ">"); } } protected static void assertLessThanOrEqual(int expectedMax, int actual) { if (actual > expectedMax) { fail("expectedMax: <" + expectedMax + ">, actual: <" + actual + ">"); } } protected static void assertRange(short expectedMin, short expectedMax, short actual) { if (actual < expectedMin) { fail("expectedMin: <" + expectedMin + ">, actual: <" + actual + ">"); } if (actual > expectedMax) { fail("expectedMax: <" + expectedMax + ">, actual: <" + actual + ">"); } } protected static void assertLargerThanOrEqual(short expectedMin, short actual) { if (actual < expectedMin) { fail("expectedMin: <" + expectedMin + ">, actual: <" + actual + ">"); } } protected static void assertLessThanOrEqual(short expectedMax, short actual) { if (actual > expectedMax) { fail("expectedMax: <" + expectedMax + ">, actual: <" + actual + ">"); } } protected static void assertNotEquals(byte notExpected, byte actual) { if (actual == notExpected) { fail("notExpected: <" + notExpected + ">, actual: <" + actual + ">"); } } protected static void assertNotEquals(short notExpected, short actual) { if (actual == notExpected) { fail("notExpected: <" + notExpected + ">, actual: <" + actual + ">"); } } protected static void assertNotEquals(int notExpected, int actual) { if (actual == notExpected) { fail("notExpected: <" + notExpected + ">, actual: <" + actual + ">"); } } protected static void assertNotEquals(float notExpected, float actual) { if (actual == notExpected) { fail("notExpected: <" + notExpected + ">, actual: <" + actual + ">"); } } // // Utils // protected void setDataSourceForCommonTests(IBasicMediaPlayer player, Object args) throws IOException { player.setDataSource(getStorageFilePath(LOCAL_440HZ_STEREO_MP3)); } protected void setDataSourceForPlaybackCompletedTest(IBasicMediaPlayer player, Object args) throws IOException { player.setDataSource(getStorageFilePath(LOCAL_SHORT_SILENT_STEREO_MP3)); } protected void setDataSourceForPreparingTest(IBasicMediaPlayer player, Object args) throws IOException { final Uri uri = ONLINE_URI_3MIN_WHITENOISE_WAV; checkOnlineConnectionStates(); checkFileExistsOnHTTP(uri); player.setDataSource(getContext(), uri); } protected void checkFileExistsOnHTTP(Uri uri) { final String failmsg = "Specified file is not accessible; uri = " + uri; final int MAX_RETRIES = 5; HttpURLConnection.setFollowRedirects(false); Exception exception = null; for (int retry = 1; retry <= MAX_RETRIES; retry++) { HttpURLConnection con = null; try { con = (HttpURLConnection) new URL(uri.toString()).openConnection(); con.setConnectTimeout(1000); con.setReadTimeout(1000); con.setRequestMethod("HEAD"); con.disconnect(); assertEquals(failmsg, HttpURLConnection.HTTP_OK, con.getResponseCode()); return; } catch (SocketTimeoutException e) { Log.i(getName(), "checkFileExistsOnHTTP() - SocketTimeoutException - retry " + retry); exception = e; } catch (EOFException e) { Log.i(getName(), "checkFileExistsOnHTTP() - EOFException - retry " + retry); exception = e; } catch (Exception e) { fail(failmsg + ", exception = " + e); } finally { if (con != null) { con.disconnect(); con = null; } } } if (exception != null) { fail(failmsg + ", exception = " + exception); } } protected void checkOnlineConnectionStates() { // Check WiFi connection is enabled ConnectivityManager connManager = (ConnectivityManager) getContext(). getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo active = connManager.getActiveNetworkInfo(); assertTrue("WiFi is not enabled", (active != null) && (active.getType() == ConnectivityManager.TYPE_WIFI)); } protected void resetPlayerStates(final IBasicMediaPlayer player) { player.reset(); player.setOnPreparedListener(null); player.setOnCompletionListener(null); player.setOnBufferingUpdateListener(null); player.setOnSeekCompleteListener(null); player.setOnErrorListener(null); player.setOnInfoListener(null); } protected TestBasicMediaPlayerWrapper createWrappedPlayerInstance() { return TestBasicMediaPlayerWrapper.create(this, mFactory); } protected static IBasicMediaPlayer unwrap(IBasicMediaPlayer player) { if (player instanceof TestBasicMediaPlayerWrapper) { return ((TestBasicMediaPlayerWrapper) player).getWrappedInstance(); } return player; } protected static boolean isStandardPlayer(IBasicMediaPlayer player) { return (unwrap(player) instanceof StandardMediaPlayer); } protected static boolean isStandardPlayer(IMediaPlayerFactory factory) { return (factory instanceof StandardMediaPlayerFactory); } protected void playLocalFileAndReset(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForCommonTests(player, args); player.prepare(); player.start(); player.stop(); resetPlayerStates(player); } protected static int determineWaitCompletionTime(IBasicMediaPlayer player) { return safeGetDuration(player, DURATION_LIMIT) + DEFAULT_EVENT_WAIT_DURATION; } // TestObjectBaseWrapper.Host @Override public void hostRunTestOnUiThread(Runnable r) { try { runTestOnUiThread(r); } catch (RuntimeException e) { throw e; } catch (Throwable th) { fail(th.toString()); } } @Override public void hostRunTestOnUiThread(final ThrowableRunnable thr) throws Throwable { final Throwable[] throwable = new Throwable[1]; runTestOnUiThread(new Runnable() { @Override public void run() { try { thr.run(); } catch (Throwable th) { throwable[0] = th; } } }); if (throwable[0] != null) { throw throwable[0]; } } @Override public void hostTestFail() { fail(); } @Override public void hostTestFail(String message) { fail(message); } // // State transition // protected void transitStateToIdle(IBasicMediaPlayer player, Object args) throws IOException { player.reset(); } protected void transitStateToInitialized(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForCommonTests(player, args); } protected void transitStateToPreparing(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForPreparingTest(player, args); if (optCanTransitToPreparingStateAutomatically()) { player.prepareAsync(); } } protected void transitStateToPrepared(IBasicMediaPlayer player, Object args) throws IOException { PreparedListenerObject prepared = new PreparedListenerObject(); setDataSourceForCommonTests(player, args); player.setOnPreparedListener(prepared); player.prepare(); if (!prepared.await(SHORT_EVENT_WAIT_DURATION)) { fail("onPrepared() wasn't called properly"); } player.setOnPreparedListener(null); } protected void transitStateToStarted(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForCommonTests(player, args); player.prepare(); if (optCanTransitToStartedStateAutomatically()) { player.start(); } } protected void transitStateToPaused(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForCommonTests(player, args); player.prepare(); player.start(); player.pause(); } protected void transitStateToStopped(IBasicMediaPlayer player, Object args) throws IOException { setDataSourceForCommonTests(player, args); player.prepare(); player.start(); player.stop(); } protected void transitStateToPlaybackCompleted(IBasicMediaPlayer player, Object args) throws IOException { CompletionListenerObject comp = new CompletionListenerObject(); setDataSourceForPlaybackCompletedTest(player, args); player.prepare(); player.setOnCompletionListener(comp); player.start(); if (!comp.await(determineWaitCompletionTime(player))) { fail("transitStateToPlaybackCompleted() failed"); } player.setOnCompletionListener(null); } protected void transitStateToErrorBeforePrepared(IBasicMediaPlayer player, Object args) throws IOException { final Object sharedSyncObj = new Object(); final ErrorListenerObject err = new ErrorListenerObject(sharedSyncObj, false); final CompletionListenerObject comp = new CompletionListenerObject(sharedSyncObj); player.setOnErrorListener(err); player.setOnCompletionListener(comp); player.getDuration(); if (!comp.await(DEFAULT_EVENT_WAIT_DURATION)) { fail("transitStateToErrorBeforePrepared() failed, " + comp + ", " + err); } player.setOnErrorListener(null); player.setOnCompletionListener(null); assertTrue(err.occurred()); assertTrue(comp.occurred()); } protected void transitStateToErrorAfterPrepared(IBasicMediaPlayer player, Object args) throws IOException { final Object sharedSyncObj = new Object(); final ErrorListenerObject err = new ErrorListenerObject(sharedSyncObj, false); final CompletionListenerObject comp = new CompletionListenerObject(sharedSyncObj); player.setOnErrorListener(err); player.setOnCompletionListener(comp); setDataSourceForCommonTests(player, args); player.prepare(); player.pause(); if (!comp.await(DEFAULT_EVENT_WAIT_DURATION)) { fail("transitStateToErrorAfterPrepared() failed, " + comp + ", " + err); } player.setOnErrorListener(null); player.setOnCompletionListener(null); assertTrue(err.occurred()); assertTrue(comp.occurred()); } protected void transitStateToEnd(IBasicMediaPlayer player, Object args) throws IOException { player.release(); } protected void transitState( PlayerState playerState, IBasicMediaPlayer player, Object args) throws IOException { switch (playerState) { case Idle: transitStateToIdle(player, args); break; case Initialized: transitStateToInitialized(player, args); break; case Preparing: transitStateToPreparing(player, args); break; case Prepared: transitStateToPrepared(player, args); break; case Started: transitStateToStarted(player, args); break; case Paused: transitStateToPaused(player, args); break; case PlaybackCompleted: transitStateToPlaybackCompleted(player, args); break; case Stopped: transitStateToStopped(player, args); break; case ErrorBeforePrepared: transitStateToErrorBeforePrepared(player, args); break; case ErrorAfterPrepared: transitStateToPrepared(player, args); break; case End: transitStateToEnd(player, args); break; } } // // Utility // protected static TestCase makeSingleBasicTest( Class<? extends TestCase> clazz, String testName, Class<? extends IMediaPlayerFactory> factoryClazz) { try { Method method = clazz.getMethod(testName); int modifiers = method.getModifiers(); if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && testName.startsWith("test") && method.getReturnType().equals(void.class)) { return clazz .getConstructor(ParameterizedTestArgs.class) .newInstance(new ParameterizedTestArgs( testName, new BasicTestParams(factoryClazz))); } else { throw new IllegalArgumentException("Specified test method is not available " + clazz + ", " + testName); } } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } catch (InstantiationException e) { throw new IllegalStateException(e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } // // Options // protected boolean optCanTransitToPreparingStateAutomatically() { // Override and return false if prepareAsync() should not be called // in transitStateToPreparing() return true; } protected boolean optCanTransitToStartedStateAutomatically() { // Override and return false if start() should not be called // in transitStateToStarted() return true; } }