1 /* 2 * Copyright (C) 2017 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 17 package com.example.android.pictureinpicture 18 19 import android.content.pm.ActivityInfo 20 import androidx.test.InstrumentationRegistry 21 import androidx.test.espresso.Espresso.onView 22 import androidx.test.espresso.UiController 23 import androidx.test.espresso.ViewAction 24 import androidx.test.espresso.action.ViewActions.click 25 import androidx.test.espresso.assertion.ViewAssertions.matches 26 import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom 27 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 28 import androidx.test.espresso.matcher.ViewMatchers.withId 29 import androidx.test.rule.ActivityTestRule 30 import androidx.test.runner.AndroidJUnit4 31 import android.support.v4.media.session.PlaybackStateCompat 32 import android.view.View 33 import com.example.android.pictureinpicture.widget.MovieView 34 import org.hamcrest.Description 35 import org.hamcrest.Matcher 36 import org.hamcrest.Matchers.not 37 import org.hamcrest.TypeSafeMatcher 38 import org.hamcrest.core.AllOf.allOf 39 import org.hamcrest.core.Is.`is` 40 import org.hamcrest.core.IsEqual.equalTo 41 import org.junit.Assert.assertNotNull 42 import org.junit.Assert.assertThat 43 import org.junit.Assert.assertTrue 44 import org.junit.Rule 45 import org.junit.Test 46 import org.junit.runner.RunWith 47 48 49 @RunWith(AndroidJUnit4::class) 50 class MediaSessionPlaybackActivityTest { 51 52 @Rule @JvmField 53 val rule = ActivityTestRule(MediaSessionPlaybackActivity::class.java) 54 55 @Test 56 fun movie_playingOnPip() { 57 // The movie should be playing on start 58 onView(withId(R.id.movie)) 59 .check(matches(allOf(isDisplayed(), isPlaying()))) 60 .perform(showControls()) 61 // Click on the button to enter Picture-in-Picture mode 62 onView(withId(R.id.minimize)).perform(click()) 63 InstrumentationRegistry.getInstrumentation().waitForIdleSync() 64 // The Activity is paused. We cannot use Espresso to test paused activities. 65 rule.runOnUiThread { 66 // We are now in Picture-in-Picture mode 67 assertTrue(rule.activity.isInPictureInPictureMode) 68 val view = rule.activity.findViewById<MovieView>(R.id.movie) 69 assertNotNull(view) 70 // The video should still be playing 71 assertTrue(view.isPlaying) 72 73 // The media session state should be playing. 74 assertMediaStateIs(PlaybackStateCompat.STATE_PLAYING) 75 } 76 } 77 78 @Test 79 fun movie_pauseAndResume() { 80 // The movie should be playing on start 81 onView(withId(R.id.movie)) 82 .check(matches(allOf(isDisplayed(), isPlaying()))) 83 .perform(showControls()) 84 // Pause 85 onView(withId(R.id.toggle)).perform(click()) 86 onView(withId(R.id.movie)).check(matches(not(isPlaying()))) 87 // The media session state should be paused. 88 assertMediaStateIs(PlaybackStateCompat.STATE_PAUSED) 89 // Resume 90 onView(withId(R.id.toggle)).perform(click()) 91 onView(withId(R.id.movie)).check(matches(isPlaying())) 92 // The media session state should be playing. 93 assertMediaStateIs(PlaybackStateCompat.STATE_PLAYING) 94 } 95 96 @Test 97 fun fullscreen_enabledOnLandscape() { 98 rule.runOnUiThread { rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } 99 InstrumentationRegistry.getInstrumentation().waitForIdleSync() 100 rule.runOnUiThread { 101 val decorView = rule.activity.window.decorView 102 assertThat(decorView.systemUiVisibility, 103 hasFlag(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) 104 } 105 } 106 107 @Test 108 fun fullscreen_disabledOnPortrait() { 109 rule.runOnUiThread { 110 rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 111 } 112 InstrumentationRegistry.getInstrumentation().waitForIdleSync() 113 rule.runOnUiThread { 114 val decorView = rule.activity.window.decorView 115 assertThat(decorView.systemUiVisibility, 116 not(hasFlag(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN))) 117 } 118 } 119 120 private fun assertMediaStateIs(@PlaybackStateCompat.State expectedState: Int) { 121 val state = rule.activity.mediaController.playbackState 122 assertNotNull(state) 123 assertThat( 124 "MediaSession is not in the correct state", 125 state?.state, 126 `is`<Int>(equalTo<Int>(expectedState))) 127 } 128 129 private fun isPlaying(): Matcher<View> { 130 return object : TypeSafeMatcher<View>() { 131 override fun matchesSafely(view: View): Boolean { 132 return (view as MovieView).isPlaying 133 } 134 135 override fun describeTo(description: Description) { 136 description.appendText("MovieView is playing") 137 } 138 } 139 } 140 141 private fun showControls(): ViewAction { 142 return object : ViewAction { 143 override fun getConstraints(): Matcher<View> { 144 return isAssignableFrom(MovieView::class.java) 145 } 146 147 override fun getDescription(): String { 148 return "Show controls of MovieView" 149 } 150 151 override fun perform(uiController: UiController, view: View) { 152 uiController.loopMainThreadUntilIdle() 153 (view as MovieView).showControls() 154 uiController.loopMainThreadUntilIdle() 155 } 156 } 157 } 158 159 private fun hasFlag(flag: Int): Matcher<in Int> { 160 return object : TypeSafeMatcher<Int>() { 161 override fun matchesSafely(i: Int?): Boolean { 162 return i?.and(flag) == flag 163 } 164 165 override fun describeTo(description: Description) { 166 description.appendText("Flag integer contains " + flag) 167 } 168 } 169 } 170 171 } 172