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.camera2.internal
17 
18 import android.os.Build
19 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
20 import androidx.camera.core.CameraState
21 import androidx.camera.core.CameraState.ERROR_CAMERA_IN_USE
22 import androidx.camera.core.CameraState.ERROR_MAX_CAMERAS_IN_USE
23 import androidx.camera.core.CameraState.StateError
24 import androidx.camera.core.CameraState.Type
25 import androidx.camera.core.impl.CameraInternal
26 import androidx.camera.core.impl.CameraStateRegistry
27 import androidx.camera.core.impl.utils.executor.CameraXExecutors
28 import androidx.camera.testing.fakes.FakeCamera
29 import androidx.camera.testing.impl.fakes.FakeCameraCoordinator
30 import androidx.lifecycle.Observer
31 import com.google.common.truth.Truth.assertThat
32 import org.junit.Rule
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 import org.robolectric.RobolectricTestRunner
36 import org.robolectric.annotation.Config
37 import org.robolectric.annotation.internal.DoNotInstrument
38 
39 @RunWith(RobolectricTestRunner::class)
40 @DoNotInstrument
41 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
42 internal class CameraStateMachineTest {
43 
44     @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
45 
46     private val cameraCoordinator = FakeCameraCoordinator()
47 
48     /** Wrapper method that initializes the required test parameters, then runs the test's body. */
49     private fun runTest(body: (CameraStateMachine, StateObserver) -> Unit) {
50         val registry = CameraStateRegistry(cameraCoordinator, 1)
51         val stateMachine = CameraStateMachine(registry)
52         val stateObserver = StateObserver()
53         stateMachine.stateLiveData.observeForever(stateObserver)
54 
55         // Test body
56         body.invoke(stateMachine, stateObserver)
57 
58         stateMachine.stateLiveData.removeObserver(stateObserver)
59     }
60 
61     @Test
62     fun shouldEmitClosedStateInitially() = runTest { _, stateObserver ->
63         stateObserver.assertHasState(CameraState.create(Type.CLOSED)).assertHasNoMoreStates()
64     }
65 
66     @Test
67     fun shouldNotEmitNewState_whenStateHasNotChanged() = runTest { stateMachine, stateObserver ->
68         stateMachine.updateState(CameraInternal.State.OPENING, null)
69         stateMachine.updateState(CameraInternal.State.OPENING, null)
70 
71         stateObserver
72             .assertHasState(CameraState.create(Type.CLOSED))
73             .assertHasState(CameraState.create(Type.OPENING))
74             .assertHasNoMoreStates()
75     }
76 
77     @Test
78     fun shouldNotEmitNewState_whenStateAndErrorHaveNotChanged() =
79         runTest { stateMachine, stateObserver ->
80             stateMachine.updateState(
81                 CameraInternal.State.OPENING,
82                 StateError.create(ERROR_CAMERA_IN_USE)
83             )
84             stateMachine.updateState(
85                 CameraInternal.State.OPENING,
86                 StateError.create(ERROR_CAMERA_IN_USE)
87             )
88 
89             stateObserver
90                 .assertHasState(CameraState.create(Type.CLOSED))
91                 .assertHasState(
92                     CameraState.create(Type.OPENING, StateError.create(ERROR_CAMERA_IN_USE))
93                 )
94                 .assertHasNoMoreStates()
95         }
96 
97     @Test
98     fun shouldEmitNewState_whenStateChanges() = runTest { stateMachine, stateObserver ->
99         stateMachine.updateState(CameraInternal.State.OPENING, null)
100         stateMachine.updateState(CameraInternal.State.OPEN, null)
101 
102         stateObserver
103             .assertHasState(CameraState.create(Type.CLOSED))
104             .assertHasState(CameraState.create(Type.OPENING))
105             .assertHasState(CameraState.create(Type.OPEN))
106             .assertHasNoMoreStates()
107     }
108 
109     @Test
110     fun shouldNotEmitNewState_whenInConfiguredState() = runTest { stateMachine, stateObserver ->
111         stateMachine.updateState(CameraInternal.State.OPENING, null)
112         stateMachine.updateState(CameraInternal.State.OPEN, null)
113         stateMachine.updateState(CameraInternal.State.CONFIGURED, null)
114 
115         stateObserver
116             .assertHasState(CameraState.create(Type.CLOSED))
117             .assertHasState(CameraState.create(Type.OPENING))
118             .assertHasState(CameraState.create(Type.OPEN))
119             .assertHasNoMoreStates()
120     }
121 
122     @Test
123     fun shouldEmitNewState_whenErrorChanges() = runTest { stateMachine, stateObserver ->
124         stateMachine.updateState(
125             CameraInternal.State.OPENING,
126             StateError.create(ERROR_CAMERA_IN_USE)
127         )
128         stateMachine.updateState(
129             CameraInternal.State.OPENING,
130             StateError.create(ERROR_MAX_CAMERAS_IN_USE)
131         )
132 
133         stateObserver
134             .assertHasState(CameraState.create(Type.CLOSED))
135             .assertHasState(
136                 CameraState.create(Type.OPENING, StateError.create(ERROR_CAMERA_IN_USE))
137             )
138             .assertHasState(
139                 CameraState.create(Type.OPENING, StateError.create(ERROR_MAX_CAMERAS_IN_USE))
140             )
141             .assertHasNoMoreStates()
142     }
143 
144     @Test
145     fun shouldEmitOpeningState_whenCameraIsOpening_whileAnotherIsClosing() {
146         val registry = CameraStateRegistry(cameraCoordinator, 1)
147         val stateMachine = CameraStateMachine(registry)
148 
149         // Create, open then start closing first camera
150         val camera1 = FakeCamera()
151         registry.registerCamera(camera1, CameraXExecutors.directExecutor(), {}, {})
152         registry.tryOpenCamera(camera1)
153         registry.markCameraState(camera1, CameraInternal.State.OPEN)
154         registry.markCameraState(camera1, CameraInternal.State.CLOSING)
155 
156         // Create and try to open second camera. Since the first camera is still closing, its
157         // internal state will move to PENDING_OPEN
158         val camera2 = FakeCamera()
159         registry.registerCamera(camera2, CameraXExecutors.directExecutor(), {}, {})
160         registry.tryOpenCamera(camera2)
161         registry.markCameraState(camera2, CameraInternal.State.PENDING_OPEN)
162 
163         // Get the second camera's public state
164         stateMachine.updateState(CameraInternal.State.PENDING_OPEN, null)
165         val newState = stateMachine.stateLiveData.value
166         assertThat(newState).isEqualTo(CameraState.create(Type.OPENING))
167     }
168 
169     class StateObserver : Observer<CameraState> {
170 
171         private val states = mutableListOf<CameraState>()
172         private var index = 0
173 
174         override fun onChanged(value: CameraState) {
175             states.add(value)
176         }
177 
178         fun assertHasState(expectedState: CameraState): StateObserver {
179             val state = states[index++]
180             assertThat(state).isEqualTo(expectedState)
181             return this
182         }
183 
184         fun assertHasNoMoreStates() {
185             assertThat(index).isEqualTo(states.size)
186         }
187     }
188 }
189