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