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