app/src/androidTest/kotlin/space/subread/overlay/PlayerProviderTest.kt (4205 bytes)
1 package space.subread.overlay 2 3 import android.media.session.MediaSession 4 import android.media.session.PlaybackState 5 import android.os.SystemClock 6 import androidx.core.net.toUri 7 import androidx.test.ext.junit.runners.AndroidJUnit4 8 import androidx.test.platform.app.InstrumentationRegistry 9 import org.junit.Assert.assertEquals 10 import org.junit.Assert.assertTrue 11 import org.junit.Assume.assumeTrue 12 import org.junit.Test 13 import org.junit.runner.RunWith 14 15 /** 16 * The provider that a reader app asks for the position. The test app plays the part of the 17 * player with its own media session. The device must give this app notification access, else 18 * the test is skipped. 19 */ 20 @RunWith(AndroidJUnit4::class) 21 class PlayerProviderTest { 22 23 private val context = InstrumentationRegistry.getInstrumentation().targetContext 24 private val uri = "content://${PlayerProvider.AUTHORITY}/state".toUri() 25 26 private fun state(state: Int, position: Long, speed: Float = 1f) = PlaybackState.Builder() 27 .setState(state, position, speed, SystemClock.elapsedRealtime()) 28 .setActions(PlaybackState.ACTION_PLAY or PlaybackState.ACTION_PAUSE or PlaybackState.ACTION_SEEK_TO) 29 .build() 30 31 private fun read(): Map<String, String> { 32 val cursor = context.contentResolver.query(uri, null, null, null, null)!! 33 cursor.use { 34 assertTrue(it.moveToFirst()) 35 val line = it.getString(it.getColumnIndexOrThrow(PlayerProvider.COLUMN_STATE)) 36 return line.split(';').associate { part -> part.substringBefore('=') to part.substringAfter('=') } 37 } 38 } 39 40 @Test 41 fun theStateLineHasThePositionOfNow() { 42 val paused = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 1_000, 1f, 5_000).build() 43 assertEquals( 44 "playing=0;position=1000;speed=1.0;package=p", 45 PlayerProvider.stateLine(paused, "p", 9_000), 46 ) 47 val playing = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 1_000, 2f, 5_000).build() 48 assertEquals( 49 "playing=1;position=9000;speed=2.0;package=p", 50 PlayerProvider.stateLine(playing, "p", 9_000), 51 ) 52 val unknown = PlaybackState.Builder() 53 .setState(PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1f, 0).build() 54 assertEquals("playing=1;position=-1;speed=1.0;package=p", PlayerProvider.stateLine(unknown, "p", 9_000)) 55 } 56 57 @Test 58 fun aReaderAppReadsThePlayerAndControlsIt() { 59 assumeTrue("notification access is not given to this app", MediaListener.isAllowed(context)) 60 val session = MediaSession(context, "test player") 61 val seeks = mutableListOf<Long>() 62 var plays = 0 63 var pauses = 0 64 session.setCallback(object : MediaSession.Callback() { 65 override fun onPlay() { plays++ } 66 override fun onPause() { pauses++ } 67 override fun onSeekTo(pos: Long) { seeks += pos } 68 }) 69 try { 70 session.setPlaybackState(state(PlaybackState.STATE_PLAYING, 96_000, speed = 1.5f)) 71 session.isActive = true 72 73 val seen = read() 74 assertEquals("1", seen["playing"]) 75 assertEquals("1.5", seen["speed"]) 76 assertEquals(context.packageName, seen["package"]) 77 val position = seen["position"]!!.toLong() 78 assertTrue("position $position is not near 96000", position in 96_000..97_500) 79 80 val resolver = context.contentResolver 81 resolver.call(uri, PlayerProvider.METHOD_PAUSE, null, null) 82 resolver.call(uri, PlayerProvider.METHOD_PLAY, null, null) 83 val answer = resolver.call(uri, PlayerProvider.METHOD_SEEK, "12345", null)!! 84 val until = SystemClock.elapsedRealtime() + 5_000 85 while ((plays < 1 || pauses < 1 || seeks.isEmpty()) && SystemClock.elapsedRealtime() < until) Thread.sleep(20) 86 assertEquals(1, plays) 87 assertEquals(1, pauses) 88 assertEquals(listOf(12_345L), seeks) 89 assertTrue(answer.getString(PlayerProvider.COLUMN_STATE)!!.startsWith("playing=")) 90 } finally { 91 session.release() 92 } 93 } 94 }