1 /* <lambda>null2 * Copyright 2019 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 androidx.camera.lifecycle 18 19 import android.content.Context 20 import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT 21 import android.graphics.Rect 22 import android.os.Handler 23 import android.os.Looper 24 import android.util.Rational 25 import android.view.Surface 26 import androidx.annotation.OptIn 27 import androidx.annotation.RequiresApi 28 import androidx.camera.camera2.Camera2Config 29 import androidx.camera.camera2.pipe.integration.CameraPipeConfig 30 import androidx.camera.core.CameraFilter 31 import androidx.camera.core.CameraInfo 32 import androidx.camera.core.CameraSelector 33 import androidx.camera.core.CameraSelector.LENS_FACING_BACK 34 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT 35 import androidx.camera.core.CameraXConfig 36 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig 37 import androidx.camera.core.ImageAnalysis 38 import androidx.camera.core.ImageCapture 39 import androidx.camera.core.Preview 40 import androidx.camera.core.UseCase 41 import androidx.camera.core.UseCaseGroup 42 import androidx.camera.core.ViewPort 43 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED 44 import androidx.camera.core.impl.AdapterCameraInfo 45 import androidx.camera.core.impl.CameraConfig 46 import androidx.camera.core.impl.CameraFactory 47 import androidx.camera.core.impl.CameraInfoInternal 48 import androidx.camera.core.impl.CameraThreadConfig 49 import androidx.camera.core.impl.Config 50 import androidx.camera.core.impl.ExtendedCameraConfigProviderStore 51 import androidx.camera.core.impl.Identifier 52 import androidx.camera.core.impl.MutableOptionsBundle 53 import androidx.camera.core.impl.SessionProcessor 54 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType 55 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor 56 import androidx.camera.core.internal.utils.ImageUtil 57 import androidx.camera.testing.fakes.FakeAppConfig 58 import androidx.camera.testing.fakes.FakeCamera 59 import androidx.camera.testing.fakes.FakeCameraInfoInternal 60 import androidx.camera.testing.impl.CameraPipeConfigTestRule 61 import androidx.camera.testing.impl.CameraUtil 62 import androidx.camera.testing.impl.fakes.FakeCameraConfig 63 import androidx.camera.testing.impl.fakes.FakeCameraCoordinator 64 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager 65 import androidx.camera.testing.impl.fakes.FakeCameraFactory 66 import androidx.camera.testing.impl.fakes.FakeCameraFilter 67 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner 68 import androidx.camera.testing.impl.fakes.FakeSessionProcessor 69 import androidx.camera.testing.impl.fakes.FakeSurfaceEffect 70 import androidx.camera.testing.impl.fakes.FakeSurfaceProcessor 71 import androidx.camera.testing.impl.fakes.FakeUseCase 72 import androidx.camera.testing.impl.fakes.FakeUseCaseConfig 73 import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory 74 import androidx.camera.video.Recorder 75 import androidx.camera.video.VideoCapture 76 import androidx.concurrent.futures.await 77 import androidx.test.core.app.ApplicationProvider 78 import androidx.test.filters.SdkSuppress 79 import androidx.test.filters.SmallTest 80 import androidx.testutils.assertThrows 81 import com.google.common.truth.Truth.assertThat 82 import kotlinx.coroutines.Dispatchers 83 import kotlinx.coroutines.MainScope 84 import kotlinx.coroutines.runBlocking 85 import org.junit.After 86 import org.junit.Assume.assumeTrue 87 import org.junit.Before 88 import org.junit.Rule 89 import org.junit.Test 90 import org.junit.runner.RunWith 91 import org.junit.runners.Parameterized 92 93 @SmallTest 94 @RunWith(Parameterized::class) 95 @SdkSuppress(minSdkVersion = 21) 96 class ProcessCameraProviderTest( 97 private val implName: String, 98 private val cameraConfig: CameraXConfig, 99 ) { 100 101 @get:Rule 102 val cameraPipeConfigTestRule = 103 CameraPipeConfigTestRule( 104 active = implName.contains(CameraPipeConfig::class.simpleName!!), 105 ) 106 107 @get:Rule 108 val cameraRule = 109 CameraUtil.grantCameraPermissionAndPreTestAndPostTest( 110 CameraUtil.PreTestCameraIdList(cameraConfig) 111 ) 112 113 companion object { 114 @JvmStatic 115 @Parameterized.Parameters(name = "{0}") 116 fun data() = 117 listOf( 118 arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()), 119 arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig()) 120 ) 121 } 122 123 private val context = ApplicationProvider.getApplicationContext() as Context 124 private val lifecycleOwner0 = FakeLifecycleOwner() 125 private val lifecycleOwner1 = FakeLifecycleOwner() 126 private val cameraCoordinator = FakeCameraCoordinator() 127 private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 128 129 private lateinit var provider: ProcessCameraProvider 130 131 @Before 132 fun setUp() { 133 assumeTrue(CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!)) 134 } 135 136 @After 137 fun tearDown() { 138 runBlocking(MainScope().coroutineContext) { 139 try { 140 val provider = ProcessCameraProvider.getInstance(context).await() 141 provider.shutdownAsync().await() 142 } catch (e: IllegalStateException) { 143 // ProcessCameraProvider may not be configured. Ignore. 144 } 145 } 146 } 147 148 @Test 149 fun bindUseCaseGroupWithEffect_effectIsSetOnUseCase() { 150 // Arrange. 151 ProcessCameraProvider.configureInstance(cameraConfig) 152 val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor()) 153 val effect = FakeSurfaceEffect(mainThreadExecutor(), surfaceProcessor) 154 val preview = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 155 val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview).addEffect(effect).build() 156 157 runBlocking(MainScope().coroutineContext) { 158 // Act. 159 provider = ProcessCameraProvider.getInstance(context).await() 160 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCaseGroup) 161 162 // Assert. 163 assertThat(preview.effect).isEqualTo(effect) 164 assertThat(provider.isConcurrentCameraModeOn).isFalse() 165 } 166 } 167 168 @OptIn(ExperimentalCameraProviderConfiguration::class) 169 @Test 170 fun canRetrieveCamera_withZeroUseCases() { 171 ProcessCameraProvider.configureInstance(cameraConfig) 172 runBlocking(MainScope().coroutineContext) { 173 provider = ProcessCameraProvider.getInstance(context).await() 174 val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector) 175 assertThat(camera).isNotNull() 176 assertThat(provider.isConcurrentCameraModeOn).isFalse() 177 } 178 } 179 180 @Test 181 fun bindUseCase_isBound() { 182 ProcessCameraProvider.configureInstance(cameraConfig) 183 184 runBlocking(MainScope().coroutineContext) { 185 provider = ProcessCameraProvider.getInstance(context).await() 186 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 187 188 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase) 189 190 assertThat(provider.isBound(useCase)).isTrue() 191 assertThat(provider.isConcurrentCameraModeOn).isFalse() 192 } 193 } 194 195 @Test 196 fun bindSecondUseCaseToDifferentLifecycle_firstUseCaseStillBound() { 197 ProcessCameraProvider.configureInstance(cameraConfig) 198 199 runBlocking(MainScope().coroutineContext) { 200 provider = ProcessCameraProvider.getInstance(context).await() 201 202 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 203 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 204 205 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0) 206 provider.bindToLifecycle(lifecycleOwner1, cameraSelector, useCase1) 207 208 // TODO(b/158595693) Add check on whether or not camera for fakeUseCase0 should be 209 // exist or not 210 // assertThat(fakeUseCase0.camera).isNotNull() (or isNull()?) 211 assertThat(provider.isBound(useCase0)).isTrue() 212 assertThat(useCase1.camera).isNotNull() 213 assertThat(provider.isBound(useCase1)).isTrue() 214 assertThat(provider.isConcurrentCameraModeOn).isFalse() 215 } 216 } 217 218 @Test 219 fun isNotBound_afterUnbind() { 220 ProcessCameraProvider.configureInstance(cameraConfig) 221 222 runBlocking(MainScope().coroutineContext) { 223 provider = ProcessCameraProvider.getInstance(context).await() 224 225 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 226 227 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase) 228 229 provider.unbind(useCase) 230 231 assertThat(provider.isBound(useCase)).isFalse() 232 assertThat(provider.isConcurrentCameraModeOn).isFalse() 233 } 234 } 235 236 @Test 237 fun unbindFirstUseCase_secondUseCaseStillBound() { 238 ProcessCameraProvider.configureInstance(cameraConfig) 239 240 runBlocking(MainScope().coroutineContext) { 241 provider = ProcessCameraProvider.getInstance(context).await() 242 243 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 244 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 245 246 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0, useCase1) 247 248 provider.unbind(useCase0) 249 250 assertThat(useCase0.camera).isNull() 251 assertThat(provider.isBound(useCase0)).isFalse() 252 assertThat(useCase1.camera).isNotNull() 253 assertThat(provider.isBound(useCase1)).isTrue() 254 assertThat(provider.isConcurrentCameraModeOn).isFalse() 255 } 256 } 257 258 @Test 259 fun unbindAll_unbindsAllUseCasesFromCameras() { 260 ProcessCameraProvider.configureInstance(cameraConfig) 261 262 runBlocking(MainScope().coroutineContext) { 263 provider = ProcessCameraProvider.getInstance(context).await() 264 265 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 266 267 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase) 268 269 provider.unbindAll() 270 271 assertThat(useCase.camera).isNull() 272 assertThat(provider.isBound(useCase)).isFalse() 273 assertThat(provider.isConcurrentCameraModeOn).isFalse() 274 } 275 } 276 277 @Test 278 fun bindMultipleUseCases() { 279 ProcessCameraProvider.configureInstance(cameraConfig) 280 281 runBlocking(MainScope().coroutineContext) { 282 provider = ProcessCameraProvider.getInstance(context).await() 283 284 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 285 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 286 287 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0, useCase1) 288 289 assertThat(provider.isBound(useCase0)).isTrue() 290 assertThat(provider.isBound(useCase1)).isTrue() 291 assertThat(provider.isConcurrentCameraModeOn).isFalse() 292 } 293 } 294 295 @Test 296 fun bind_createsDifferentLifecycleCameras_forDifferentLifecycles() { 297 ProcessCameraProvider.configureInstance(cameraConfig) 298 299 runBlocking(MainScope().coroutineContext) { 300 provider = ProcessCameraProvider.getInstance(context).await() 301 302 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 303 val camera0 = provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0) 304 305 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 306 val camera1 = provider.bindToLifecycle(lifecycleOwner1, cameraSelector, useCase1) 307 308 assertThat(camera0).isNotEqualTo(camera1) 309 assertThat(provider.isConcurrentCameraModeOn).isFalse() 310 } 311 } 312 313 @Test 314 fun bind_returnTheSameCameraForSameSelectorAndLifecycleOwner() { 315 ProcessCameraProvider.configureInstance(cameraConfig) 316 317 runBlocking(MainScope().coroutineContext) { 318 provider = ProcessCameraProvider.getInstance(context).await() 319 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 320 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 321 322 val camera0 = provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0) 323 val camera1 = provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase1) 324 325 assertThat(camera0).isSameInstanceAs(camera1) 326 assertThat(provider.isConcurrentCameraModeOn).isFalse() 327 } 328 } 329 330 @Test 331 fun bindUseCases_withDifferentLensFacingButSameLifecycleOwner() { 332 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 333 ProcessCameraProvider.configureInstance(cameraConfig) 334 335 runBlocking(MainScope().coroutineContext) { 336 provider = ProcessCameraProvider.getInstance(context).await() 337 338 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 339 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 340 341 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0) 342 343 assertThrows<IllegalArgumentException> { 344 provider.bindToLifecycle( 345 lifecycleOwner0, 346 CameraSelector.DEFAULT_FRONT_CAMERA, 347 useCase1 348 ) 349 } 350 assertThat(provider.isConcurrentCameraModeOn).isFalse() 351 } 352 } 353 354 @Test 355 fun bindUseCases_withDifferentLensFacingAndLifecycle() { 356 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 357 ProcessCameraProvider.configureInstance(cameraConfig) 358 359 runBlocking(MainScope().coroutineContext) { 360 provider = ProcessCameraProvider.getInstance(context).await() 361 362 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 363 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 364 365 val camera0 = provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase0) 366 367 val camera1 = 368 provider.bindToLifecycle( 369 lifecycleOwner1, 370 CameraSelector.DEFAULT_FRONT_CAMERA, 371 useCase1 372 ) 373 374 assertThat(camera0).isNotEqualTo(camera1) 375 assertThat(provider.isConcurrentCameraModeOn).isFalse() 376 } 377 } 378 379 @Test 380 fun bindUseCases_withNotExistedLensFacingCamera() { 381 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 382 val cameraFactoryProvider = 383 CameraFactory.Provider { _, _, _, _ -> 384 val cameraFactory = FakeCameraFactory() 385 cameraFactory.insertCamera(LENS_FACING_BACK, "0") { 386 FakeCamera("0", null, FakeCameraInfoInternal("0", 0, LENS_FACING_BACK)) 387 } 388 cameraFactory.cameraCoordinator = FakeCameraCoordinator() 389 cameraFactory 390 } 391 392 val appConfigBuilder = 393 CameraXConfig.Builder() 394 .setCameraFactoryProvider(cameraFactoryProvider) 395 .setDeviceSurfaceManagerProvider { _, _, _ -> FakeCameraDeviceSurfaceManager() } 396 .setUseCaseConfigFactoryProvider { FakeUseCaseConfigFactory() } 397 398 ProcessCameraProvider.configureInstance(appConfigBuilder.build()) 399 400 runBlocking(MainScope().coroutineContext) { 401 provider = ProcessCameraProvider.getInstance(context).await() 402 403 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 404 405 // The front camera is not defined, we should get the IllegalArgumentException when it 406 // tries to get the camera. 407 assertThrows<IllegalArgumentException> { 408 provider.bindToLifecycle( 409 lifecycleOwner0, 410 CameraSelector.DEFAULT_FRONT_CAMERA, 411 useCase 412 ) 413 } 414 assertThat(provider.isConcurrentCameraModeOn).isFalse() 415 } 416 } 417 418 @Test 419 fun bindUseCases_viewPortUpdated() { 420 runBlocking(MainScope().coroutineContext) { 421 // Arrange. 422 ProcessCameraProvider.configureInstance(cameraConfig) 423 provider = ProcessCameraProvider.awaitInstance(context) 424 val preview = Preview.Builder().build() 425 val imageCapture = ImageCapture.Builder().build() 426 val imageAnalysis = ImageAnalysis.Builder().build() 427 val videoCapture = VideoCapture.Builder(Recorder.Builder().build()).build() 428 val aspectRatio = Rational(2, 1) 429 val viewPort = ViewPort.Builder(aspectRatio, Surface.ROTATION_0).build() 430 431 // Act. 432 provider.bindToLifecycle( 433 FakeLifecycleOwner(), 434 cameraSelector, 435 UseCaseGroup.Builder() 436 .setViewPort(viewPort) 437 .addUseCase(preview) 438 .addUseCase(imageCapture) 439 .addUseCase(imageAnalysis) 440 .addUseCase(videoCapture) 441 .build() 442 ) 443 444 // Assert: The aspect ratio of the use cases should be close to the aspect ratio of the 445 // view port set to the UseCaseGroup. 446 val aspectRatioThreshold = 0.01 447 assertThat(preview.viewPortCropRect!!.aspectRatio().toDouble()) 448 .isWithin(aspectRatioThreshold) 449 .of(preview.getExpectedAspectRatio(aspectRatio)) 450 assertThat(imageCapture.viewPortCropRect!!.aspectRatio().toDouble()) 451 .isWithin(aspectRatioThreshold) 452 .of(imageCapture.getExpectedAspectRatio(aspectRatio)) 453 assertThat(imageAnalysis.viewPortCropRect!!.aspectRatio().toDouble()) 454 .isWithin(aspectRatioThreshold) 455 .of(imageAnalysis.getExpectedAspectRatio(aspectRatio)) 456 assertThat(videoCapture.viewPortCropRect!!.aspectRatio().toDouble()) 457 .isWithin(aspectRatioThreshold) 458 .of(videoCapture.getExpectedAspectRatio(aspectRatio)) 459 } 460 } 461 462 private fun UseCase.getExpectedAspectRatio(aspectRatio: Rational): Double { 463 val camera = this.camera!! 464 val isStreamSharingOn = !camera.hasTransform 465 // If stream sharing is on, the expected aspect ratio doesn't have to be adjusted with 466 // sensor rotation. 467 val rotation = if (isStreamSharingOn) 0 else camera.cameraInfo.sensorRotationDegrees 468 return ImageUtil.getRotatedAspectRatio(rotation, aspectRatio).toDouble() 469 } 470 471 @Test 472 fun lifecycleCameraIsNotActive_withZeroUseCases_bindBeforeLifecycleStarted() { 473 ProcessCameraProvider.configureInstance(cameraConfig) 474 runBlocking(MainScope().coroutineContext) { 475 provider = ProcessCameraProvider.getInstance(context).await() 476 val camera: LifecycleCamera = 477 provider.bindToLifecycle(lifecycleOwner0, cameraSelector) as LifecycleCamera 478 lifecycleOwner0.startAndResume() 479 assertThat(camera.isActive).isFalse() 480 assertThat(provider.isConcurrentCameraModeOn).isFalse() 481 } 482 } 483 484 @Test 485 fun lifecycleCameraIsNotActive_withZeroUseCases_bindAfterLifecycleStarted() { 486 ProcessCameraProvider.configureInstance(cameraConfig) 487 runBlocking(MainScope().coroutineContext) { 488 provider = ProcessCameraProvider.getInstance(context).await() 489 lifecycleOwner0.startAndResume() 490 val camera: LifecycleCamera = 491 provider.bindToLifecycle(lifecycleOwner0, cameraSelector) as LifecycleCamera 492 assertThat(camera.isActive).isFalse() 493 assertThat(provider.isConcurrentCameraModeOn).isFalse() 494 } 495 } 496 497 @Test 498 fun lifecycleCameraIsActive_withUseCases_bindBeforeLifecycleStarted() { 499 ProcessCameraProvider.configureInstance(cameraConfig) 500 runBlocking(MainScope().coroutineContext) { 501 provider = ProcessCameraProvider.getInstance(context).await() 502 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 503 val camera: LifecycleCamera = 504 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase) 505 as LifecycleCamera 506 lifecycleOwner0.startAndResume() 507 assertThat(camera.isActive).isTrue() 508 assertThat(provider.isConcurrentCameraModeOn).isFalse() 509 } 510 } 511 512 @Test 513 fun lifecycleCameraIsActive_withUseCases_bindAfterLifecycleStarted() { 514 ProcessCameraProvider.configureInstance(cameraConfig) 515 runBlocking(MainScope().coroutineContext) { 516 provider = ProcessCameraProvider.getInstance(context).await() 517 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 518 lifecycleOwner0.startAndResume() 519 val camera: LifecycleCamera = 520 provider.bindToLifecycle(lifecycleOwner0, cameraSelector, useCase) 521 as LifecycleCamera 522 assertThat(camera.isActive).isTrue() 523 assertThat(provider.isConcurrentCameraModeOn).isFalse() 524 } 525 } 526 527 @Test 528 fun lifecycleCameraIsNotActive_bindAfterLifecycleDestroyed() { 529 ProcessCameraProvider.configureInstance(cameraConfig) 530 runBlocking(MainScope().coroutineContext) { 531 provider = ProcessCameraProvider.getInstance(context).await() 532 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 533 lifecycleOwner0.destroy() 534 val camera: LifecycleCamera = 535 provider.bindToLifecycle( 536 lifecycleOwner0, 537 CameraSelector.DEFAULT_BACK_CAMERA, 538 useCase 539 ) as LifecycleCamera 540 assertThat(camera.isActive).isFalse() 541 } 542 } 543 544 @Test 545 fun lifecycleCameraIsNotActive_unbindUseCase() { 546 ProcessCameraProvider.configureInstance(cameraConfig) 547 runBlocking(MainScope().coroutineContext) { 548 provider = ProcessCameraProvider.getInstance(context).await() 549 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 550 lifecycleOwner0.startAndResume() 551 val camera: LifecycleCamera = 552 provider.bindToLifecycle( 553 lifecycleOwner0, 554 CameraSelector.DEFAULT_BACK_CAMERA, 555 useCase 556 ) as LifecycleCamera 557 assertThat(camera.isActive).isTrue() 558 provider.unbind(useCase) 559 assertThat(camera.isActive).isFalse() 560 assertThat(provider.isConcurrentCameraModeOn).isFalse() 561 } 562 } 563 564 @Test 565 fun lifecycleCameraIsNotActive_unbindAll() { 566 ProcessCameraProvider.configureInstance(cameraConfig) 567 runBlocking(MainScope().coroutineContext) { 568 provider = ProcessCameraProvider.getInstance(context).await() 569 val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 570 lifecycleOwner0.startAndResume() 571 val camera: LifecycleCamera = 572 provider.bindToLifecycle( 573 lifecycleOwner0, 574 CameraSelector.DEFAULT_BACK_CAMERA, 575 useCase 576 ) as LifecycleCamera 577 assertThat(camera.isActive).isTrue() 578 provider.unbindAll() 579 assertThat(camera.isActive).isFalse() 580 assertThat(provider.isConcurrentCameraModeOn).isFalse() 581 } 582 } 583 584 @Test 585 fun getAvailableCameraInfos_usesAllCameras() { 586 ProcessCameraProvider.configureInstance(cameraConfig) 587 runBlocking { 588 provider = ProcessCameraProvider.getInstance(context).await() 589 val cameraCount = 590 cameraConfig 591 .getCameraFactoryProvider(null)!! 592 .newInstance( 593 context, 594 CameraThreadConfig.create( 595 mainThreadExecutor(), 596 Handler(Looper.getMainLooper()) 597 ), 598 null, 599 -1L 600 ) 601 .availableCameraIds 602 .size 603 604 assertThat(provider.availableCameraInfos.size).isEqualTo(cameraCount) 605 } 606 } 607 608 @Test 609 fun getAvailableCameraInfos_usesFilteredCameras() { 610 ProcessCameraProvider.configureInstance( 611 FakeAppConfig.create(CameraSelector.DEFAULT_BACK_CAMERA) 612 ) 613 runBlocking { 614 provider = ProcessCameraProvider.getInstance(context).await() 615 616 val cameraInfos = provider.availableCameraInfos 617 assertThat(cameraInfos.size).isEqualTo(1) 618 619 val cameraInfo = cameraInfos.first() as FakeCameraInfoInternal 620 assertThat(cameraInfo.lensFacing).isEqualTo(LENS_FACING_BACK) 621 } 622 } 623 624 @Test 625 fun getCameraInfo_sameCameraInfoWithBindToLifecycle_afterBinding() { 626 // Arrange. 627 ProcessCameraProvider.configureInstance(cameraConfig) 628 629 runBlocking(MainScope().coroutineContext) { 630 provider = ProcessCameraProvider.getInstance(context).await() 631 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 632 633 // Act: getting the camera info after bindToLifecycle. 634 val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector) 635 val cameraInfoInternal1: CameraInfoInternal = 636 provider.getCameraInfo(cameraSelector) as CameraInfoInternal 637 val cameraInfoInternal2: CameraInfoInternal = camera.cameraInfo as CameraInfoInternal 638 639 // Assert. 640 assertThat(cameraInfoInternal1).isSameInstanceAs(cameraInfoInternal2) 641 } 642 } 643 644 @Test 645 fun getCameraInfo_sameCameraInfoWithBindToLifecycle_beforeBinding() { 646 // Arrange. 647 ProcessCameraProvider.configureInstance(cameraConfig) 648 runBlocking(MainScope().coroutineContext) { 649 provider = ProcessCameraProvider.getInstance(context).await() 650 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 651 652 // Act: getting the camera info before bindToLifecycle. 653 val cameraInfoInternal1: CameraInfoInternal = 654 provider.getCameraInfo(cameraSelector) as CameraInfoInternal 655 val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector) 656 val cameraInfoInternal2: CameraInfoInternal = camera.cameraInfo as CameraInfoInternal 657 658 // Assert. 659 assertThat(cameraInfoInternal1).isSameInstanceAs(cameraInfoInternal2) 660 } 661 } 662 663 @Test 664 fun getCameraInfo_containExtendedCameraConfig() { 665 // Arrange. 666 ProcessCameraProvider.configureInstance(cameraConfig) 667 runBlocking { 668 provider = ProcessCameraProvider.getInstance(context).await() 669 val id = Identifier.create("FakeId") 670 val cameraConfig = FakeCameraConfig(postviewSupported = true) 671 ExtendedCameraConfigProviderStore.addConfig(id) { _, _ -> cameraConfig } 672 val cameraSelector = 673 CameraSelector.Builder().addCameraFilter(FakeCameraFilter(id)).build() 674 675 // Act. 676 val adapterCameraInfo = provider.getCameraInfo(cameraSelector) as AdapterCameraInfo 677 678 // Assert. 679 assertThat(adapterCameraInfo.isPostviewSupported).isTrue() 680 } 681 } 682 683 @Test 684 fun getCameraInfo_exceptionWhenCameraSelectorInvalid() { 685 // Arrange. 686 ProcessCameraProvider.configureInstance(cameraConfig) 687 runBlocking(MainScope().coroutineContext) { 688 provider = ProcessCameraProvider.getInstance(context).await() 689 // Intentionally create a camera selector that doesn't result in a camera. 690 val cameraSelector = 691 CameraSelector.Builder().addCameraFilter { ArrayList<CameraInfo>() }.build() 692 693 // Act & Assert. 694 assertThrows(IllegalArgumentException::class.java) { 695 provider.getCameraInfo(cameraSelector) 696 } 697 } 698 } 699 700 @Test 701 fun getAvailableConcurrentCameraInfos() { 702 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 703 runBlocking { 704 provider = ProcessCameraProvider.getInstance(context).await() 705 assertThat(provider.availableConcurrentCameraInfos.size).isEqualTo(2) 706 assertThat(provider.availableConcurrentCameraInfos[0].size).isEqualTo(2) 707 assertThat(provider.availableConcurrentCameraInfos[1].size).isEqualTo(2) 708 } 709 } 710 711 @Test 712 fun shutdown_clearsPreviousConfiguration() { 713 ProcessCameraProvider.configureInstance(FakeAppConfig.create()) 714 715 runBlocking { 716 provider = ProcessCameraProvider.getInstance(context).await() 717 // Clear the configuration so we can reinit 718 provider.shutdownAsync().await() 719 } 720 721 // Should not throw exception 722 ProcessCameraProvider.configureInstance(FakeAppConfig.create()) 723 assertThat(cameraCoordinator.cameraOperatingMode) 724 .isEqualTo(CAMERA_OPERATING_MODE_UNSPECIFIED) 725 assertThat(cameraCoordinator.concurrentCameraSelectors).isEmpty() 726 assertThat(cameraCoordinator.activeConcurrentCameraInfos).isEmpty() 727 } 728 729 @Test 730 fun bindConcurrentCamera_isBound() { 731 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 732 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 733 734 runBlocking(MainScope().coroutineContext) { 735 provider = ProcessCameraProvider.getInstance(context).await() 736 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 737 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 738 739 val singleCameraConfig0 = 740 SingleCameraConfig( 741 CameraSelector.DEFAULT_BACK_CAMERA, 742 UseCaseGroup.Builder().addUseCase(useCase0).build(), 743 lifecycleOwner0 744 ) 745 val singleCameraConfig1 = 746 SingleCameraConfig( 747 CameraSelector.DEFAULT_FRONT_CAMERA, 748 UseCaseGroup.Builder().addUseCase(useCase1).build(), 749 lifecycleOwner1 750 ) 751 752 if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) { 753 val concurrentCamera = 754 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 755 756 assertThat(concurrentCamera).isNotNull() 757 assertThat(concurrentCamera.cameras.size).isEqualTo(2) 758 assertThat(provider.isBound(useCase0)).isTrue() 759 assertThat(provider.isBound(useCase1)).isTrue() 760 assertThat(provider.isConcurrentCameraModeOn).isTrue() 761 } else { 762 assertThrows<UnsupportedOperationException> { 763 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 764 } 765 } 766 } 767 } 768 769 @Test 770 fun bindConcurrentPhysicalCamera_isBound() { 771 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 772 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 773 774 runBlocking(MainScope().coroutineContext) { 775 provider = ProcessCameraProvider.getInstance(context).await() 776 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 777 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 778 779 val singleCameraConfig0 = 780 SingleCameraConfig( 781 CameraSelector.Builder() 782 .requireLensFacing(CameraSelector.LENS_FACING_FRONT) 783 .build(), 784 UseCaseGroup.Builder().addUseCase(useCase0).build(), 785 lifecycleOwner0 786 ) 787 val singleCameraConfig1 = 788 SingleCameraConfig( 789 CameraSelector.Builder() 790 .requireLensFacing(CameraSelector.LENS_FACING_FRONT) 791 .build(), 792 UseCaseGroup.Builder().addUseCase(useCase1).build(), 793 lifecycleOwner0 794 ) 795 796 val concurrentCamera = 797 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 798 799 assertThat(concurrentCamera).isNotNull() 800 assertThat(concurrentCamera.cameras.size).isEqualTo(1) 801 assertThat(provider.isBound(useCase0)).isTrue() 802 assertThat(provider.isBound(useCase1)).isTrue() 803 assertThat(provider.isConcurrentCameraModeOn).isFalse() 804 } 805 } 806 807 @Test 808 fun bindConcurrentCameraTwice_isBound() { 809 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 810 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 811 812 runBlocking(MainScope().coroutineContext) { 813 provider = ProcessCameraProvider.getInstance(context).await() 814 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 815 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 816 val useCase2 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 817 818 val singleCameraConfig0 = 819 SingleCameraConfig( 820 CameraSelector.DEFAULT_BACK_CAMERA, 821 UseCaseGroup.Builder().addUseCase(useCase0).build(), 822 lifecycleOwner0 823 ) 824 val singleCameraConfig1 = 825 SingleCameraConfig( 826 CameraSelector.DEFAULT_FRONT_CAMERA, 827 UseCaseGroup.Builder().addUseCase(useCase1).build(), 828 lifecycleOwner1 829 ) 830 val singleCameraConfig2 = 831 SingleCameraConfig( 832 CameraSelector.DEFAULT_FRONT_CAMERA, 833 UseCaseGroup.Builder().addUseCase(useCase2).build(), 834 lifecycleOwner1 835 ) 836 837 if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) { 838 val concurrentCamera0 = 839 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 840 841 assertThat(concurrentCamera0).isNotNull() 842 assertThat(concurrentCamera0.cameras.size).isEqualTo(2) 843 assertThat(provider.isBound(useCase0)).isTrue() 844 assertThat(provider.isBound(useCase1)).isTrue() 845 assertThat(provider.isConcurrentCameraModeOn).isTrue() 846 } else { 847 assertThrows<UnsupportedOperationException> { 848 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 849 } 850 } 851 852 if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) { 853 val concurrentCamera1 = 854 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig2)) 855 856 assertThat(concurrentCamera1).isNotNull() 857 assertThat(concurrentCamera1.cameras.size).isEqualTo(2) 858 assertThat(provider.isBound(useCase0)).isTrue() 859 assertThat(provider.isBound(useCase2)).isTrue() 860 assertThat(provider.isConcurrentCameraModeOn).isTrue() 861 } else { 862 assertThrows<UnsupportedOperationException> { 863 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig2)) 864 } 865 } 866 } 867 } 868 869 @Test 870 fun bindConcurrentCamera_lessThanTwoSingleCameraConfigs() { 871 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 872 873 runBlocking(MainScope().coroutineContext) { 874 provider = ProcessCameraProvider.getInstance(context).await() 875 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 876 877 val singleCameraConfig0 = 878 SingleCameraConfig( 879 CameraSelector.DEFAULT_BACK_CAMERA, 880 UseCaseGroup.Builder().addUseCase(useCase0).build(), 881 lifecycleOwner0 882 ) 883 884 assertThrows<IllegalArgumentException> { 885 provider.bindToLifecycle(listOf(singleCameraConfig0)) 886 } 887 } 888 } 889 890 @Test 891 fun bindConcurrentCamera_moreThanTwoSingleCameraConfigs() { 892 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 893 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 894 895 runBlocking(MainScope().coroutineContext) { 896 provider = ProcessCameraProvider.getInstance(context).await() 897 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 898 val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 899 900 val singleCameraConfig0 = 901 SingleCameraConfig( 902 CameraSelector.DEFAULT_BACK_CAMERA, 903 UseCaseGroup.Builder().addUseCase(useCase0).build(), 904 lifecycleOwner0 905 ) 906 val singleCameraConfig1 = 907 SingleCameraConfig( 908 CameraSelector.DEFAULT_FRONT_CAMERA, 909 UseCaseGroup.Builder().addUseCase(useCase1).build(), 910 lifecycleOwner1 911 ) 912 val singleCameraConfig2 = 913 SingleCameraConfig( 914 CameraSelector.DEFAULT_FRONT_CAMERA, 915 UseCaseGroup.Builder().addUseCase(useCase0).build(), 916 lifecycleOwner1 917 ) 918 919 assertThrows<java.lang.IllegalArgumentException> { 920 provider.bindToLifecycle( 921 listOf(singleCameraConfig0, singleCameraConfig1, singleCameraConfig2) 922 ) 923 } 924 } 925 } 926 927 @Test 928 fun bindConcurrentCamera_isDualRecording() { 929 assumeTrue(CameraUtil.hasCameraWithLensFacing(LENS_FACING_FRONT)) 930 ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig()) 931 932 runBlocking(MainScope().coroutineContext) { 933 provider = ProcessCameraProvider.getInstance(context).await() 934 val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build() 935 val useCase1 = 936 FakeUseCase( 937 FakeUseCaseConfig.Builder(CaptureType.VIDEO_CAPTURE).useCaseConfig, 938 CaptureType.VIDEO_CAPTURE 939 ) 940 941 val singleCameraConfig0 = 942 SingleCameraConfig( 943 CameraSelector.DEFAULT_BACK_CAMERA, 944 UseCaseGroup.Builder().addUseCase(useCase0).addUseCase(useCase1).build(), 945 lifecycleOwner0 946 ) 947 val singleCameraConfig1 = 948 SingleCameraConfig( 949 CameraSelector.DEFAULT_FRONT_CAMERA, 950 UseCaseGroup.Builder().addUseCase(useCase0).addUseCase(useCase1).build(), 951 lifecycleOwner1 952 ) 953 954 if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) { 955 val concurrentCamera = 956 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 957 958 assertThat(concurrentCamera).isNotNull() 959 assertThat(concurrentCamera.cameras.size).isEqualTo(1) 960 assertThat(provider.isBound(useCase0)).isTrue() 961 assertThat(provider.isBound(useCase1)).isTrue() 962 assertThat(provider.isConcurrentCameraModeOn).isTrue() 963 } else { 964 assertThrows<UnsupportedOperationException> { 965 provider.bindToLifecycle(listOf(singleCameraConfig0, singleCameraConfig1)) 966 } 967 } 968 } 969 } 970 971 @Test 972 @SdkSuppress(minSdkVersion = 23) 973 fun bindWithExtensions_doesNotImpactPreviousCamera(): Unit = 974 runBlocking(Dispatchers.Main) { 975 // 1. Arrange. 976 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 977 val cameraSelectorWithExtensions = 978 getCameraSelectorWithLimitedCapabilities( 979 cameraSelector, 980 emptySet() // All capabilities are not supported. 981 ) 982 ProcessCameraProvider.configureInstance(cameraConfig) 983 provider = ProcessCameraProvider.getInstance(context).await() 984 val useCase = Preview.Builder().build() 985 986 // 2. Act: bind with and then without Extensions. 987 // bind with regular cameraSelector to get the regular camera (with empty use cases) 988 val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector) 989 // bind with extensions cameraSelector to get the restricted version of camera. 990 val cameraWithExtensions = 991 provider.bindToLifecycle(lifecycleOwner0, cameraSelectorWithExtensions, useCase) 992 993 // 3. Assert: ensure we can different instances of Camera and one does not affect the 994 // other. 995 assertThat(camera).isNotSameInstanceAs(cameraWithExtensions) 996 997 // the Extensions CameraControl does not support the zoom. 998 assertThrows<IllegalStateException> { 999 cameraWithExtensions.cameraControl.setZoomRatio(1.0f).await() 1000 } 1001 1002 // only the Extensions CameraInfo does not support the zoom. 1003 assertThat(camera.cameraInfo.zoomState.value!!.maxZoomRatio).isGreaterThan(1.0f) 1004 assertThat(cameraWithExtensions.cameraInfo.zoomState.value!!.maxZoomRatio) 1005 .isEqualTo(1.0f) 1006 } 1007 1008 @RequiresApi(23) 1009 private fun getCameraSelectorWithLimitedCapabilities( 1010 cameraSelector: CameraSelector, 1011 supportedCapabilities: Set<Int> 1012 ): CameraSelector { 1013 val identifier = Identifier.create("idStr") 1014 val sessionProcessor = 1015 FakeSessionProcessor(supportedCameraOperations = supportedCapabilities) 1016 ExtendedCameraConfigProviderStore.addConfig(identifier) { _, _ -> 1017 object : CameraConfig { 1018 override fun getConfig(): Config { 1019 return MutableOptionsBundle.create() 1020 } 1021 1022 override fun getCompatibilityId(): Identifier { 1023 return identifier 1024 } 1025 1026 override fun getSessionProcessor(valueIfMissing: SessionProcessor?) = 1027 sessionProcessor 1028 1029 override fun getSessionProcessor() = sessionProcessor 1030 } 1031 } 1032 1033 val builder = CameraSelector.Builder.fromSelector(cameraSelector) 1034 builder.addCameraFilter( 1035 object : CameraFilter { 1036 override fun filter(cameraInfos: MutableList<CameraInfo>): MutableList<CameraInfo> { 1037 val newCameraInfos = mutableListOf<CameraInfo>() 1038 newCameraInfos.addAll(cameraInfos) 1039 return newCameraInfos 1040 } 1041 1042 override fun getIdentifier(): Identifier { 1043 return identifier 1044 } 1045 } 1046 ) 1047 1048 return builder.build() 1049 } 1050 1051 private fun createConcurrentCameraAppConfig(): CameraXConfig { 1052 val combination0 = 1053 mapOf( 1054 "0" to CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build(), 1055 "1" to CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build() 1056 ) 1057 val combination1 = 1058 mapOf( 1059 "0" to CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build(), 1060 "2" to CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build() 1061 ) 1062 1063 cameraCoordinator.addConcurrentCameraIdsAndCameraSelectors(combination0) 1064 cameraCoordinator.addConcurrentCameraIdsAndCameraSelectors(combination1) 1065 val cameraFactoryProvider = 1066 CameraFactory.Provider { _, _, _, _ -> 1067 val cameraFactory = FakeCameraFactory() 1068 cameraFactory.insertCamera(LENS_FACING_BACK, "0") { 1069 FakeCamera("0", null, FakeCameraInfoInternal("0", 0, LENS_FACING_BACK)) 1070 } 1071 cameraFactory.insertCamera(LENS_FACING_FRONT, "1") { 1072 FakeCamera("1", null, FakeCameraInfoInternal("1", 0, LENS_FACING_FRONT)) 1073 } 1074 cameraFactory.insertCamera(LENS_FACING_FRONT, "2") { 1075 FakeCamera("2", null, FakeCameraInfoInternal("2", 0, LENS_FACING_FRONT)) 1076 } 1077 cameraFactory.cameraCoordinator = cameraCoordinator 1078 cameraFactory 1079 } 1080 val appConfigBuilder = 1081 CameraXConfig.Builder() 1082 .setCameraFactoryProvider(cameraFactoryProvider) 1083 .setDeviceSurfaceManagerProvider { _, _, _ -> FakeCameraDeviceSurfaceManager() } 1084 .setUseCaseConfigFactoryProvider { FakeUseCaseConfigFactory() } 1085 1086 return appConfigBuilder.build() 1087 } 1088 1089 private fun Rect.aspectRatio(rotationDegrees: Int = 0): Rational { 1090 return if (rotationDegrees % 180 != 0) Rational(height(), width()) 1091 else Rational(width(), height()) 1092 } 1093 } 1094