1 /*
2  * Copyright (C) 2022 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.android.systemui.statusbar.policy
18 
19 import android.content.pm.PackageManager
20 import android.hardware.camera2.CameraCharacteristics
21 import android.hardware.camera2.CameraManager
22 import android.hardware.camera2.impl.CameraMetadataNative
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.broadcast.BroadcastSender
27 import com.android.systemui.dump.DumpManager
28 import com.android.systemui.util.concurrency.FakeExecutor
29 import com.android.systemui.util.mockito.argumentCaptor
30 import com.android.systemui.util.mockito.capture
31 import com.android.systemui.util.settings.FakeSettings
32 import com.android.systemui.util.time.FakeSystemClock
33 import java.util.concurrent.Executor
34 import org.junit.Before
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.Mock
38 import org.mockito.Mockito.any
39 import org.mockito.Mockito.clearInvocations
40 import org.mockito.Mockito.eq
41 import org.mockito.Mockito.never
42 import org.mockito.Mockito.verify
43 import org.mockito.Mockito.verifyNoMoreInteractions
44 import org.mockito.Mockito.verifyZeroInteractions
45 import org.mockito.Mockito.`when`
46 import org.mockito.MockitoAnnotations
47 
48 @SmallTest
49 @RunWith(AndroidJUnit4::class)
50 class FlashlightControllerImplTest : SysuiTestCase() {
51 
52     @Mock
53     private lateinit var dumpManager: DumpManager
54 
55     @Mock
56     private lateinit var cameraManager: CameraManager
57 
58     @Mock
59     private lateinit var broadcastSender: BroadcastSender
60 
61     @Mock
62     private lateinit var packageManager: PackageManager
63 
64     private lateinit var fakeSettings: FakeSettings
65     private lateinit var fakeSystemClock: FakeSystemClock
66     private lateinit var backgroundExecutor: FakeExecutor
67     private lateinit var controller: FlashlightControllerImpl
68 
69     @Before
setupnull70     fun setup() {
71         MockitoAnnotations.initMocks(this)
72 
73         fakeSystemClock = FakeSystemClock()
74         backgroundExecutor = FakeExecutor(fakeSystemClock)
75         fakeSettings = FakeSettings()
76 
77         `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
78                 .thenReturn(true)
79 
80         controller = FlashlightControllerImpl(
81                 dumpManager,
82                 cameraManager,
83                 backgroundExecutor,
84                 fakeSettings,
85                 broadcastSender,
86                 packageManager
87         )
88     }
89 
90     @Test
testNoCameraManagerInteractionDirectlyOnConstructornull91     fun testNoCameraManagerInteractionDirectlyOnConstructor() {
92         verifyZeroInteractions(cameraManager)
93     }
94 
95     @Test
testCameraManagerInitAfterConstructionOnExecutornull96     fun testCameraManagerInitAfterConstructionOnExecutor() {
97         injectCamera()
98         backgroundExecutor.runAllReady()
99 
100         verify(cameraManager).registerTorchCallback(eq(backgroundExecutor), any())
101     }
102 
103     @Test
testNoCallbackIfNoFlashCameranull104     fun testNoCallbackIfNoFlashCamera() {
105         injectCamera(flash = false)
106         backgroundExecutor.runAllReady()
107 
108         verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
109     }
110 
111     @Test
testNoCallbackIfNoBackCameranull112     fun testNoCallbackIfNoBackCamera() {
113         injectCamera(facing = CameraCharacteristics.LENS_FACING_FRONT)
114         backgroundExecutor.runAllReady()
115 
116         verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
117     }
118 
119     @Test
testSetFlashlightInBackgroundExecutornull120     fun testSetFlashlightInBackgroundExecutor() {
121         val id = injectCamera()
122         backgroundExecutor.runAllReady()
123 
124         clearInvocations(cameraManager)
125         val enable = !controller.isEnabled
126         controller.setFlashlight(enable)
127         verifyNoMoreInteractions(cameraManager)
128 
129         backgroundExecutor.runAllReady()
130         verify(cameraManager).setTorchMode(id, enable)
131     }
132 
133     @Test
testCallbackRemovedWhileDispatching_doesntCrashnull134     fun testCallbackRemovedWhileDispatching_doesntCrash() {
135         injectCamera()
136         var remove = false
137         val callback = object : FlashlightController.FlashlightListener {
138             override fun onFlashlightChanged(enabled: Boolean) {
139                 if (remove) {
140                     controller.removeCallback(this)
141                 }
142             }
143 
144             override fun onFlashlightError() {}
145 
146             override fun onFlashlightAvailabilityChanged(available: Boolean) {}
147         }
148         controller.addCallback(callback)
149         controller.addCallback(object : FlashlightController.FlashlightListener {
150             override fun onFlashlightChanged(enabled: Boolean) {}
151 
152             override fun onFlashlightError() {}
153 
154             override fun onFlashlightAvailabilityChanged(available: Boolean) {}
155         })
156         backgroundExecutor.runAllReady()
157 
158         val captor = argumentCaptor<CameraManager.TorchCallback>()
159         verify(cameraManager).registerTorchCallback(any(), capture(captor))
160         remove = true
161         captor.value.onTorchModeChanged(ID, true)
162     }
163 
injectCameranull164     private fun injectCamera(
165         flash: Boolean = true,
166         facing: Int = CameraCharacteristics.LENS_FACING_BACK
167     ): String {
168         val cameraID = ID
169         val camera = CameraCharacteristics(CameraMetadataNative().apply {
170             set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
171             set(CameraCharacteristics.LENS_FACING, facing)
172         })
173         `when`(cameraManager.cameraIdList).thenReturn(arrayOf(cameraID))
174         `when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
175         return cameraID
176     }
177 
178     companion object {
179         private const val ID = "ID"
180     }
181 }
182