1 /* <lambda>null2 * Copyright 2021 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 package androidx.camera.integration.core 17 18 import android.Manifest 19 import android.content.Context 20 import android.content.Intent 21 import androidx.camera.camera2.Camera2Config 22 import androidx.camera.camera2.pipe.integration.CameraPipeConfig 23 import androidx.camera.core.CameraInfo 24 import androidx.camera.core.CameraSelector 25 import androidx.camera.core.ImageCapture 26 import androidx.camera.core.TorchState 27 import androidx.camera.integration.core.idlingresource.WaitForViewToShow 28 import androidx.camera.lifecycle.ProcessCameraProvider 29 import androidx.camera.testing.impl.CameraPipeConfigTestRule 30 import androidx.camera.testing.impl.CameraUtil 31 import androidx.camera.testing.impl.CoreAppTestUtil 32 import androidx.camera.testing.impl.InternalTestConvenience.useInCameraTest 33 import androidx.test.core.app.ActivityScenario 34 import androidx.test.core.app.ApplicationProvider 35 import androidx.test.espresso.Espresso.onIdle 36 import androidx.test.espresso.Espresso.onView 37 import androidx.test.espresso.IdlingRegistry 38 import androidx.test.espresso.IdlingResource 39 import androidx.test.espresso.action.ViewActions.click 40 import androidx.test.espresso.assertion.ViewAssertions 41 import androidx.test.espresso.matcher.ViewMatchers 42 import androidx.test.espresso.matcher.ViewMatchers.withId 43 import androidx.test.filters.LargeTest 44 import androidx.test.platform.app.InstrumentationRegistry 45 import androidx.test.rule.GrantPermissionRule 46 import androidx.test.uiautomator.UiDevice 47 import androidx.testutils.withActivity 48 import com.google.common.truth.Truth.assertThat 49 import java.util.concurrent.TimeUnit 50 import junit.framework.AssertionFailedError 51 import kotlinx.coroutines.Dispatchers 52 import kotlinx.coroutines.runBlocking 53 import org.junit.After 54 import org.junit.Assume.assumeTrue 55 import org.junit.Before 56 import org.junit.Rule 57 import org.junit.Test 58 import org.junit.runner.RunWith 59 import org.junit.runners.Parameterized 60 61 /** Test toggle buttons in CoreTestApp. */ 62 @LargeTest 63 @RunWith(Parameterized::class) 64 class ToggleButtonUITest(private val implName: String, private val cameraConfig: String) { 65 private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 66 67 @get:Rule 68 val useCamera = 69 CameraUtil.grantCameraPermissionAndPreTestAndPostTest( 70 CameraUtil.PreTestCameraIdList( 71 if (implName == Camera2Config::class.simpleName) { 72 Camera2Config.defaultConfig() 73 } else { 74 CameraPipeConfig.defaultConfig() 75 } 76 ) 77 ) 78 79 @get:Rule 80 val permissionRule: GrantPermissionRule = 81 GrantPermissionRule.grant( 82 Manifest.permission.WRITE_EXTERNAL_STORAGE, 83 Manifest.permission.RECORD_AUDIO 84 ) 85 86 @get:Rule 87 val cameraPipeConfigTestRule = 88 CameraPipeConfigTestRule( 89 active = implName == CameraPipeConfig::class.simpleName, 90 ) 91 92 private val launchIntent = 93 Intent(ApplicationProvider.getApplicationContext(), CameraXActivity::class.java).apply { 94 putExtra(CameraXActivity.INTENT_EXTRA_CAMERA_IMPLEMENTATION, cameraConfig) 95 putExtra(CameraXActivity.INTENT_EXTRA_CAMERA_IMPLEMENTATION_NO_HISTORY, true) 96 } 97 98 @Before 99 fun setUp() { 100 assumeTrue(CameraUtil.deviceHasCamera()) 101 CoreAppTestUtil.assumeCompatibleDevice() 102 // Use the natural orientation throughout these tests to ensure the activity isn't 103 // recreated unexpectedly. This will also freeze the sensors until 104 // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be 105 // explicitly initiated from within the test. 106 device.setOrientationNatural() 107 // Clear the device UI and check if there is no dialog or lock screen on the top of the 108 // window before start the test. 109 CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation()) 110 } 111 112 @After 113 fun tearDown(): Unit = 114 runBlocking(Dispatchers.Main) { 115 // Returns to Home to restart next test. 116 device.pressHome() 117 device.waitForIdle(IDLE_TIMEOUT_MS) 118 // Unfreeze rotation so the device can choose the orientation via its own policy. Be 119 // nice 120 // to other tests :) 121 device.unfreezeRotation() 122 123 val context = ApplicationProvider.getApplicationContext<Context>() 124 val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS] 125 cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS] 126 } 127 128 @Test 129 fun testFlashToggleButton() { 130 ActivityScenario.launch<CameraXActivity>(launchIntent).useInCameraTest { scenario -> 131 // Arrange. 132 WaitForViewToShow(R.id.constraintLayout).wait() 133 assumeTrue(isButtonEnabled(R.id.flash_toggle)) 134 val useCase = scenario.withActivity { imageCapture } 135 // There are 3 different states of flash mode: ON, OFF and AUTO. 136 // By pressing flash mode toggle button, the flash mode would switch to the next state. 137 // The flash mode would loop in following sequence: OFF -> AUTO -> ON -> OFF. 138 // Act. 139 @ImageCapture.FlashMode val mode1 = useCase.flashMode 140 onView(withId(R.id.flash_toggle)).perform(click()) 141 @ImageCapture.FlashMode val mode2 = useCase.flashMode 142 onView(withId(R.id.flash_toggle)).perform(click()) 143 @ImageCapture.FlashMode val mode3 = useCase.flashMode 144 145 // Assert. 146 // After the switch, the mode2 should be different from mode1. 147 assertThat(mode2).isNotEqualTo(mode1) 148 // The mode3 should be different from first and second time. 149 assertThat(mode3).isNoneOf(mode2, mode1) 150 } 151 } 152 153 @Test 154 fun testTorchToggleButton() { 155 ActivityScenario.launch<CameraXActivity>(launchIntent).useInCameraTest { scenario -> 156 WaitForViewToShow(R.id.constraintLayout).wait() 157 assumeTrue(isButtonEnabled(R.id.torch_toggle)) 158 val cameraInfo = scenario.withActivity { cameraInfo!! } 159 val isTorchOn = cameraInfo.isTorchOn() 160 onView(withId(R.id.torch_toggle)).perform(click()) 161 assertThat(cameraInfo.isTorchOn()).isNotEqualTo(isTorchOn) 162 // By pressing the torch toggle button two times, it should switch back to original 163 // state. 164 onView(withId(R.id.torch_toggle)).perform(click()) 165 assertThat(cameraInfo.isTorchOn()).isEqualTo(isTorchOn) 166 } 167 } 168 169 @Test 170 fun testSwitchCameraToggleButton() { 171 assumeTrue( 172 "Ignore the camera switch test since there's no front camera.", 173 CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT) 174 ) 175 ActivityScenario.launch<CameraXActivity>(launchIntent).useInCameraTest { scenario -> 176 WaitForViewToShow(R.id.direction_toggle).wait() 177 assertThat(scenario.withActivity { preview }).isNotNull() 178 for (i in 0..4) { 179 scenario.waitForViewfinderIdle() 180 // Click switch camera button. 181 onView(withId(R.id.direction_toggle)).perform(click()) 182 } 183 } 184 } 185 186 private fun CameraInfo.isTorchOn(): Boolean = torchState.value == TorchState.ON 187 188 private fun isButtonEnabled(resource: Int): Boolean { 189 return try { 190 onView(withId(resource)).check(ViewAssertions.matches(ViewMatchers.isEnabled())) 191 // View is in hierarchy 192 true 193 } catch (e: AssertionFailedError) { 194 // View is not in hierarchy 195 false 196 } catch (e: Exception) { 197 // View is not in hierarchy 198 false 199 } 200 } 201 202 private fun IdlingResource.wait() { 203 IdlingRegistry.getInstance().register(this) 204 onIdle() 205 IdlingRegistry.getInstance().unregister(this) 206 } 207 208 companion object { 209 private const val IDLE_TIMEOUT_MS = 1_000L 210 211 @JvmStatic 212 @Parameterized.Parameters(name = "{0}") 213 fun data() = 214 listOf( 215 arrayOf( 216 Camera2Config::class.simpleName, 217 CameraXViewModel.CAMERA2_IMPLEMENTATION_OPTION 218 ), 219 arrayOf( 220 CameraPipeConfig::class.simpleName, 221 CameraXViewModel.CAMERA_PIPE_IMPLEMENTATION_OPTION 222 ) 223 ) 224 } 225 } 226