1 /* <lambda>null2 * Copyright 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 package androidx.camera.view 17 18 import android.content.Context 19 import android.graphics.Bitmap 20 import android.graphics.Matrix 21 import android.graphics.Rect 22 import android.graphics.drawable.ColorDrawable 23 import android.os.Build 24 import android.util.Size 25 import android.view.LayoutInflater 26 import android.view.MotionEvent 27 import android.view.Surface 28 import android.view.SurfaceView 29 import android.view.TextureView 30 import android.view.View 31 import android.view.ViewConfiguration 32 import android.widget.FrameLayout 33 import androidx.annotation.GuardedBy 34 import androidx.camera.camera2.Camera2Config 35 import androidx.camera.camera2.pipe.integration.CameraPipeConfig 36 import androidx.camera.core.Camera 37 import androidx.camera.core.CameraInfo 38 import androidx.camera.core.CameraSelector 39 import androidx.camera.core.CameraXConfig 40 import androidx.camera.core.MeteringPoint 41 import androidx.camera.core.MeteringPointFactory 42 import androidx.camera.core.Preview 43 import androidx.camera.core.SurfaceRequest 44 import androidx.camera.core.ViewPort 45 import androidx.camera.core.impl.CameraInfoInternal 46 import androidx.camera.core.impl.utils.futures.Futures 47 import androidx.camera.lifecycle.ProcessCameraProvider 48 import androidx.camera.testing.fakes.FakeCamera 49 import androidx.camera.testing.fakes.FakeCameraInfoInternal 50 import androidx.camera.testing.impl.CameraPipeConfigTestRule 51 import androidx.camera.testing.impl.CameraUtil 52 import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList 53 import androidx.camera.testing.impl.CoreAppTestUtil 54 import androidx.camera.testing.impl.fakes.FakeActivity 55 import androidx.camera.view.PreviewView.ImplementationMode 56 import androidx.camera.view.internal.compat.quirk.DeviceQuirks 57 import androidx.camera.view.internal.compat.quirk.SurfaceViewNotCroppedByParentQuirk 58 import androidx.camera.view.internal.compat.quirk.SurfaceViewStretchedQuirk 59 import androidx.camera.view.test.R 60 import androidx.core.content.ContextCompat 61 import androidx.test.core.app.ActivityScenario 62 import androidx.test.core.app.ApplicationProvider 63 import androidx.test.filters.LargeTest 64 import androidx.test.filters.SdkSuppress 65 import androidx.test.platform.app.InstrumentationRegistry 66 import androidx.test.uiautomator.UiDevice 67 import androidx.test.uiautomator.UiSelector 68 import com.google.common.truth.Truth 69 import com.google.common.truth.Truth.assertThat 70 import com.google.common.util.concurrent.ListenableFuture 71 import java.util.concurrent.CountDownLatch 72 import java.util.concurrent.Executor 73 import java.util.concurrent.Semaphore 74 import java.util.concurrent.TimeUnit 75 import java.util.concurrent.atomic.AtomicReference 76 import org.junit.After 77 import org.junit.Assume 78 import org.junit.Before 79 import org.junit.Rule 80 import org.junit.Test 81 import org.junit.runner.RunWith 82 import org.junit.runners.Parameterized 83 import org.mockito.Mockito 84 85 /** Instrumented tests for [PreviewView]. */ 86 @LargeTest 87 @RunWith(Parameterized::class) 88 @SdkSuppress(minSdkVersion = 21) 89 class PreviewViewDeviceTest(private val implName: String, private val cameraConfig: CameraXConfig) { 90 @get:Rule 91 val cameraPipeConfigTestRule = 92 CameraPipeConfigTestRule( 93 active = implName == CameraPipeConfig::class.simpleName, 94 ) 95 96 @get:Rule 97 val useCamera = 98 CameraUtil.grantCameraPermissionAndPreTestAndPostTest(PreTestCameraIdList(cameraConfig)) 99 100 private val instrumentation = InstrumentationRegistry.getInstrumentation() 101 private var activityScenario: ActivityScenario<FakeActivity>? = null 102 private val context = ApplicationProvider.getApplicationContext<Context>() 103 private var cameraProvider: ProcessCameraProvider? = null 104 private val surfaceRequestList: MutableList<SurfaceRequest> = ArrayList() 105 private var mMeteringPointFactory: MeteringPointFactory? = null 106 private val uiDevice = UiDevice.getInstance(instrumentation) 107 108 @Before 109 fun setUp() { 110 CoreAppTestUtil.prepareDeviceUI(instrumentation) 111 activityScenario = ActivityScenario.launch(FakeActivity::class.java) 112 ProcessCameraProvider.configureInstance(cameraConfig) 113 cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS] 114 } 115 116 @After 117 fun tearDown() { 118 for (surfaceRequest in surfaceRequestList) { 119 surfaceRequest.willNotProvideSurface() 120 // Ensure all successful requests have their returned future finish. 121 surfaceRequest.deferrableSurface.close() 122 } 123 if (cameraProvider != null) { 124 cameraProvider!!.shutdownAsync()[10000, TimeUnit.MILLISECONDS] 125 } 126 } 127 128 @Test 129 fun previewViewSetScaleType_controllerRebinds() { 130 // Arrange. 131 val countDownLatch = CountDownLatch(1) 132 val fitTypeSemaphore = Semaphore(0) 133 val fakeController: CameraController = 134 object : CameraController(context) { 135 override fun attachPreviewSurface( 136 surfaceProvider: Preview.SurfaceProvider, 137 viewPort: ViewPort 138 ) { 139 if (viewPort.scaleType == ViewPort.FIT) { 140 fitTypeSemaphore.release() 141 } 142 } 143 144 override fun startCamera(): Camera? { 145 return null 146 } 147 } 148 val previewViewAtomicReference = AtomicReference<PreviewView>() 149 instrumentation.runOnMainSync { 150 val previewView = PreviewView(context) 151 previewViewAtomicReference.set(previewView) 152 previewView.implementationMode = ImplementationMode.COMPATIBLE 153 notifyLatchWhenLayoutReady(previewView, countDownLatch) 154 setContentView(previewView) 155 } 156 // Wait for layout ready 157 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 158 159 // Act: set controller then change the scale type. 160 instrumentation.runOnMainSync { 161 previewViewAtomicReference.get().controller = fakeController 162 previewViewAtomicReference.get().scaleType = PreviewView.ScaleType.FIT_CENTER 163 } 164 165 // Assert: cameraController receives a fit type ViewPort. 166 Truth.assertThat(fitTypeSemaphore.tryAcquire(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)) 167 .isTrue() 168 } 169 170 @Test 171 fun receiveSurfaceRequest_transformIsValid() { 172 // Arrange: set up PreviewView. 173 val previewView = AtomicReference<PreviewView>() 174 val countDownLatch = CountDownLatch(1) 175 instrumentation.runOnMainSync { 176 previewView.set(PreviewView(context)) 177 setContentView(previewView.get()) 178 // Feed the PreviewView with a fake SurfaceRequest 179 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 180 previewView.get().surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 181 notifyLatchWhenLayoutReady(previewView.get(), countDownLatch) 182 } 183 updateCropRectAndWaitForIdle(DEFAULT_CROP_RECT) 184 185 // Assert: OutputTransform is not null. 186 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 187 instrumentation.runOnMainSync { 188 Truth.assertThat(previewView.get().outputTransform).isNotNull() 189 Truth.assertThat(previewView.get().sensorToViewTransform).isNotNull() 190 } 191 } 192 193 @Test 194 fun noSurfaceRequest_transformIsInvalid() { 195 // Arrange: set up PreviewView. 196 val previewView = AtomicReference<PreviewView>() 197 val countDownLatch = CountDownLatch(1) 198 instrumentation.runOnMainSync { 199 previewView.set(PreviewView(context)) 200 setContentView(previewView.get()) 201 notifyLatchWhenLayoutReady(previewView.get(), countDownLatch) 202 } 203 204 // Assert: OutputTransform is null. 205 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 206 instrumentation.runOnMainSync { 207 Truth.assertThat(previewView.get().outputTransform).isNull() 208 Truth.assertThat(previewView.get().sensorToViewTransform).isNull() 209 } 210 } 211 212 @Test 213 fun previewViewPinched_pinchToZoomInvokedOnController() { 214 // TODO(b/169058735): investigate and enable on Cuttlefish. 215 Assume.assumeFalse( 216 "Skip Cuttlefish until further investigation.", 217 Build.MODEL.contains("Cuttlefish") 218 ) 219 220 // Arrange. 221 val countDownLatch = CountDownLatch(1) 222 val semaphore = Semaphore(0) 223 val fakeController: CameraController = 224 object : CameraController(context) { 225 public override fun onPinchToZoom(pinchToZoomScale: Float) { 226 semaphore.release() 227 } 228 229 public override fun startCamera(): Camera? { 230 return null 231 } 232 } 233 instrumentation.runOnMainSync { 234 val previewView = PreviewView(context) 235 previewView.controller = fakeController 236 previewView.implementationMode = ImplementationMode.COMPATIBLE 237 notifyLatchWhenLayoutReady(previewView, countDownLatch) 238 setContentView(previewView) 239 } 240 // Wait for layout ready 241 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 242 243 // Act: pinch-in 80% in 100 steps. 244 uiDevice.findObject(UiSelector().index(0)).pinchIn(80, 100) 245 246 // Assert: pinch-to-zoom is called. 247 Truth.assertThat(semaphore.tryAcquire(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 248 } 249 250 @Test 251 fun previewViewClicked_tapToFocusInvokedOnController() { 252 // Arrange. 253 val countDownLatch = CountDownLatch(1) 254 val semaphore = Semaphore(0) 255 val fakeController: CameraController = 256 object : CameraController(context) { 257 public override fun onTapToFocus( 258 meteringPointFactory: MeteringPointFactory, 259 x: Float, 260 y: Float 261 ) { 262 semaphore.release() 263 } 264 265 public override fun startCamera(): Camera? { 266 return null 267 } 268 } 269 270 var clickEventHelper: ClickEventHelper? = null 271 272 instrumentation.runOnMainSync { 273 val previewView = PreviewView(context) 274 // Specifies the content description and uses it to find the view to click 275 previewView.contentDescription = previewView.hashCode().toString() 276 clickEventHelper = ClickEventHelper(previewView) 277 previewView.setOnTouchListener(clickEventHelper) 278 previewView.controller = fakeController 279 notifyLatchWhenLayoutReady(previewView, countDownLatch) 280 setContentView(previewView) 281 } 282 // Wait for layout ready 283 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 284 285 // Act: click on PreviewView 286 clickEventHelper!!.performSingleClick(uiDevice, 3) 287 288 // Assert: tap-to-focus is invoked. 289 Truth.assertThat(semaphore.tryAcquire(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 290 } 291 292 /** 293 * A helper to perform single click 294 * 295 * Some devices might have incorrect [MotionEvent#getEventTime()] and 296 * [MotionEvent#getDownTime()] info when issuing click event via UiDevice in the first time. The 297 * system time only occupies around 100ms but the event time difference is around 1000ms and 298 * then the click event is incorrectly recognized as long-click. The issue is caused by RPC 299 * operation handling timing issue. The issue causes [PreviewView] to ignore the click operation 300 * and then onTapToFocus event can't be received. The issue might be recovered in the new 301 * UiDevice click actions. 302 * 303 * This helper will help to perform the click operation and monitor the motion events to retry 304 * if a long click result is detected. 305 */ 306 private class ClickEventHelper constructor(private val targetView: View) : 307 View.OnTouchListener { 308 private val lock = Any() 309 310 @GuardedBy("lock") private var isPerformingClick = false 311 private var uiDevice: UiDevice? = null 312 private var limitedRetryCount = 0 313 private var retriedCounter = 0 314 315 override fun onTouch(view: View, event: MotionEvent): Boolean { 316 if (view != targetView) { 317 return false 318 } 319 320 if (event.action == MotionEvent.ACTION_UP) { 321 val longPressTimeout = ViewConfiguration.getLongPressTimeout() 322 323 // Retries the click action if the UP event forms a incorrect long click operation 324 // and retry counter is still under the limited retry count. 325 if ( 326 event.eventTime - event.downTime > longPressTimeout && 327 retriedCounter < limitedRetryCount 328 ) { 329 retriedCounter++ 330 performSingleClickInternal() 331 return false 332 } 333 334 // Resets member variables if incorrect long click operation is not detected or 335 // retry is not allowed any more. 336 uiDevice = null 337 limitedRetryCount = 0 338 retriedCounter = 0 339 synchronized(lock) { isPerformingClick = false } 340 } 341 342 return false 343 } 344 345 /** 346 * Perform single click action with UiDevice and will retry with the specified count when 347 * incorrect long click operation is detected. 348 * 349 * New single click request will be ignored if the previous request is still performing. 350 */ 351 fun performSingleClick(uiDevice: UiDevice, retryCount: Int = 0) { 352 synchronized(lock) { 353 if (isPerformingClick) { 354 return 355 } else { 356 isPerformingClick = true 357 } 358 } 359 360 limitedRetryCount = retryCount 361 retriedCounter = 0 362 this.uiDevice = uiDevice 363 performSingleClickInternal() 364 } 365 366 private fun performSingleClickInternal() = 367 uiDevice!! 368 .findObject(UiSelector().descriptionContains(targetView.hashCode().toString())) 369 .click() 370 } 371 372 @Test 373 fun previewView_onClickListenerWorks() { 374 // Arrange. 375 val semaphore = Semaphore(0) 376 val countDownLatch = CountDownLatch(1) 377 instrumentation.runOnMainSync { 378 val previewView = PreviewView(context) 379 previewView.setOnClickListener { semaphore.release() } 380 notifyLatchWhenLayoutReady(previewView, countDownLatch) 381 setContentView(previewView) 382 } 383 // Wait for layout ready 384 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 385 386 // Act: click on PreviewView. 387 uiDevice.findObject(UiSelector().index(0)).click() 388 389 // Assert: view is clicked. 390 Truth.assertThat(semaphore.tryAcquire(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 391 } 392 393 @Test 394 fun clearCameraController_controllerIsNull() = 395 instrumentation.runOnMainSync { 396 // Arrange. 397 val previewView = PreviewView(context) 398 setContentView(previewView) 399 val cameraController: CameraController = LifecycleCameraController(context) 400 previewView.controller = cameraController 401 Truth.assertThat(previewView.controller).isEqualTo(cameraController) 402 403 // Act and Assert. 404 previewView.controller = null 405 406 // Assert 407 Truth.assertThat(previewView.controller).isNull() 408 } 409 410 @Test 411 fun setNewCameraController_oldControllerIsCleared() { 412 instrumentation.runOnMainSync { 413 // Arrange. 414 val previewView = PreviewView(context) 415 setContentView(previewView) 416 val previewClearSemaphore = Semaphore(0) 417 val oldController: CameraController = 418 object : CameraController(context) { 419 public override fun startCamera(): Camera? { 420 return null 421 } 422 423 public override fun clearPreviewSurface() { 424 previewClearSemaphore.release() 425 } 426 } 427 previewView.controller = oldController 428 Truth.assertThat(previewView.controller).isEqualTo(oldController) 429 val newController: CameraController = LifecycleCameraController(context) 430 431 // Act and Assert. 432 previewView.controller = newController 433 434 // Assert 435 Truth.assertThat(previewView.controller).isEqualTo(newController) 436 Truth.assertThat( 437 previewClearSemaphore.tryAcquire(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) 438 ) 439 .isTrue() 440 } 441 } 442 443 @Test 444 fun usesTextureView_whenLegacyDevice() { 445 instrumentation.runOnMainSync { 446 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY) 447 val previewView = PreviewView(context) 448 setContentView(previewView) 449 previewView.implementationMode = ImplementationMode.PERFORMANCE 450 val surfaceProvider = previewView.surfaceProvider 451 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 452 Truth.assertThat(previewView.mImplementation) 453 .isInstanceOf(TextureViewImplementation::class.java) 454 } 455 } 456 457 @Test 458 fun usesTextureView_whenAPILevelNotNewerThanN() { 459 instrumentation.runOnMainSync { 460 Assume.assumeTrue(Build.VERSION.SDK_INT <= 24) 461 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 462 val previewView = PreviewView(context) 463 setContentView(previewView) 464 previewView.implementationMode = ImplementationMode.PERFORMANCE 465 val surfaceProvider = previewView.surfaceProvider 466 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 467 Truth.assertThat(previewView.mImplementation) 468 .isInstanceOf(TextureViewImplementation::class.java) 469 } 470 } 471 472 @Test 473 fun usesSurfaceView_whenNonLegacyDevice_andAPILevelNewerThanN() { 474 instrumentation.runOnMainSync { 475 Assume.assumeTrue(Build.VERSION.SDK_INT > 24) 476 Assume.assumeFalse(hasSurfaceViewQuirk()) 477 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 478 val previewView = PreviewView(context) 479 setContentView(previewView) 480 previewView.implementationMode = ImplementationMode.PERFORMANCE 481 val surfaceProvider = previewView.surfaceProvider 482 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 483 Truth.assertThat(previewView.mImplementation) 484 .isInstanceOf(SurfaceViewImplementation::class.java) 485 } 486 } 487 488 @Test 489 fun usesTextureView_whenNonLegacyDevice_andImplModeIsTextureView() { 490 instrumentation.runOnMainSync { 491 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 492 val previewView = PreviewView(context) 493 setContentView(previewView) 494 previewView.implementationMode = ImplementationMode.COMPATIBLE 495 val surfaceProvider = previewView.surfaceProvider 496 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 497 Truth.assertThat(previewView.mImplementation) 498 .isInstanceOf(TextureViewImplementation::class.java) 499 } 500 } 501 502 @Test 503 fun reuseImpl_whenImplModeIsSurfaceView_andSurfaceRequestCompatibleWithSurfaceView() { 504 instrumentation.runOnMainSync { 505 // Arrange. 506 Assume.assumeTrue(Build.VERSION.SDK_INT > 24) 507 Assume.assumeFalse(hasSurfaceViewQuirk()) 508 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 509 val previewView = PreviewView(context) 510 setContentView(previewView) 511 512 // Act. 513 previewView.implementationMode = ImplementationMode.PERFORMANCE 514 val surfaceProvider = previewView.surfaceProvider 515 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 516 val previousImplementation = previewView.mImplementation 517 assertThat(previousImplementation).isInstanceOf(SurfaceViewImplementation::class.java) 518 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 519 520 // Assert. 521 val newImplementation = previewView.mImplementation 522 assertThat(newImplementation).isEqualTo(previousImplementation) 523 } 524 } 525 526 @Test 527 fun notReuseImpl_whenImplIsTextureView() { 528 instrumentation.runOnMainSync { 529 // Arrange. 530 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 531 val previewView = PreviewView(context) 532 setContentView(previewView) 533 534 // Act. 535 previewView.implementationMode = ImplementationMode.COMPATIBLE 536 val surfaceProvider = previewView.surfaceProvider 537 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 538 val previousImplementation = previewView.mImplementation 539 assertThat(previousImplementation).isInstanceOf(TextureViewImplementation::class.java) 540 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 541 542 // Assert. 543 val newImplementation = previewView.mImplementation 544 assertThat(newImplementation).isNotEqualTo(previousImplementation) 545 } 546 } 547 548 @Test 549 fun canCreateValidMeteringPoint() { 550 val cameraInfo = 551 createCameraInfo( 552 90, 553 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 554 CameraSelector.LENS_FACING_BACK 555 ) 556 val countDownLatch = CountDownLatch(1) 557 lateinit var previewView: PreviewView 558 instrumentation.runOnMainSync { 559 previewView = PreviewView(context) 560 notifyLatchWhenLayoutReady(previewView, countDownLatch) 561 setContentView(previewView) 562 val surfaceProvider = previewView.surfaceProvider 563 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 564 } 565 // Wait for layout ready 566 Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue() 567 updateCropRectAndWaitForIdle(DEFAULT_CROP_RECT) 568 instrumentation.runOnMainSync { 569 val factory = previewView.meteringPointFactory 570 val point = factory.createPoint(100f, 100f) 571 assertPointIsValid(point) 572 } 573 } 574 575 private fun assertPointIsValid(point: MeteringPoint) { 576 Truth.assertThat(point.x in 0f..1.0f).isTrue() 577 Truth.assertThat(point.y in 0f..1.0f).isTrue() 578 } 579 580 @Test 581 fun meteringPointFactoryAutoAdjusted_whenViewSizeChange() { 582 val cameraInfo = 583 createCameraInfo( 584 90, 585 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 586 CameraSelector.LENS_FACING_BACK 587 ) 588 lateinit var previewView: PreviewView 589 instrumentation.runOnMainSync { 590 previewView = PreviewView(context) 591 mMeteringPointFactory = previewView.meteringPointFactory 592 setContentView(previewView) 593 val surfaceProvider = previewView.surfaceProvider 594 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 595 } 596 updateCropRectAndWaitForIdle(DEFAULT_CROP_RECT) 597 changeViewSize(previewView, 1000, 1000) 598 val point1 = mMeteringPointFactory!!.createPoint(100f, 100f) 599 changeViewSize(previewView, 500, 400) 600 val point2 = mMeteringPointFactory!!.createPoint(100f, 100f) 601 assertPointIsValid(point1) 602 assertPointIsValid(point2) 603 // These points should be different because the layout is changed. 604 assertPointsAreDifferent(point1, point2) 605 } 606 607 private fun changeViewSize(previewView: PreviewView?, newWidth: Int, newHeight: Int) { 608 val latchToWaitForLayoutChange = CountDownLatch(1) 609 instrumentation.runOnMainSync { 610 previewView!!.addOnLayoutChangeListener( 611 object : View.OnLayoutChangeListener { 612 override fun onLayoutChange( 613 v: View, 614 left: Int, 615 top: Int, 616 right: Int, 617 bottom: Int, 618 oldLeft: Int, 619 oldTop: Int, 620 oldRight: Int, 621 oldBottom: Int 622 ) { 623 if (previewView.width == newWidth && previewView.height == newHeight) { 624 latchToWaitForLayoutChange.countDown() 625 previewView.removeOnLayoutChangeListener(this) 626 } 627 } 628 } 629 ) 630 previewView.layoutParams = FrameLayout.LayoutParams(newWidth, newHeight) 631 } 632 633 // Wait until the new layout is changed. 634 Truth.assertThat( 635 latchToWaitForLayoutChange.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) 636 ) 637 .isTrue() 638 } 639 640 @Test 641 fun meteringPointFactoryAutoAdjusted_whenScaleTypeChanged() { 642 val cameraInfo = 643 createCameraInfo( 644 90, 645 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 646 CameraSelector.LENS_FACING_BACK 647 ) 648 lateinit var previewView: PreviewView 649 instrumentation.runOnMainSync { 650 previewView = PreviewView(context) 651 mMeteringPointFactory = previewView.meteringPointFactory 652 setContentView(previewView) 653 val surfaceProvider = previewView.surfaceProvider 654 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 655 } 656 updateCropRectAndWaitForIdle(DEFAULT_CROP_RECT) 657 658 // Surface resolution is 640x480 , set a different size for PreviewView. 659 changeViewSize(previewView, 800, 700) 660 instrumentation.runOnMainSync { previewView.scaleType = PreviewView.ScaleType.FILL_CENTER } 661 val point1 = mMeteringPointFactory!!.createPoint(100f, 100f) 662 instrumentation.runOnMainSync { previewView.scaleType = PreviewView.ScaleType.FIT_START } 663 val point2 = mMeteringPointFactory!!.createPoint(100f, 100f) 664 assertPointIsValid(point1) 665 assertPointIsValid(point2) 666 // These points should be different 667 assertPointsAreDifferent(point1, point2) 668 } 669 670 @Test 671 fun meteringPointFactoryAutoAdjusted_whenTransformationInfoChanged() { 672 val cameraInfo1 = 673 createCameraInfo( 674 90, 675 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 676 CameraSelector.LENS_FACING_BACK 677 ) 678 val cameraInfo2 = 679 createCameraInfo( 680 270, 681 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 682 CameraSelector.LENS_FACING_FRONT 683 ) 684 685 lateinit var previewView: PreviewView 686 instrumentation.runOnMainSync { 687 previewView = PreviewView(context) 688 mMeteringPointFactory = previewView.meteringPointFactory 689 setContentView(previewView) 690 val surfaceProvider = previewView.surfaceProvider 691 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo1)) 692 } 693 changeViewSize(previewView, 1000, 1000) 694 updateCropRectAndWaitForIdle(DEFAULT_CROP_RECT) 695 696 // get a MeteringPoint from a non-center point. 697 val point1 = mMeteringPointFactory!!.createPoint(100f, 120f) 698 instrumentation.runOnMainSync { 699 setContentView(previewView) 700 val surfaceProvider = previewView.surfaceProvider 701 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo2)) 702 } 703 updateCropRectAndWaitForIdle(SMALLER_CROP_RECT) 704 val point2 = mMeteringPointFactory!!.createPoint(100f, 120f) 705 assertPointIsValid(point1) 706 assertPointIsValid(point2) 707 // These points should be different 708 assertPointsAreDifferent(point1, point2) 709 } 710 711 private fun assertPointsAreDifferent(point1: MeteringPoint, point2: MeteringPoint) { 712 Truth.assertThat(point1.x != point2.x || point1.y != point2.y).isTrue() 713 } 714 715 private fun notifyLatchWhenLayoutReady( 716 previewView: PreviewView, 717 countDownLatch: CountDownLatch 718 ) { 719 previewView.addOnLayoutChangeListener( 720 object : View.OnLayoutChangeListener { 721 override fun onLayoutChange( 722 v: View, 723 left: Int, 724 top: Int, 725 right: Int, 726 bottom: Int, 727 oldLeft: Int, 728 oldTop: Int, 729 oldRight: Int, 730 oldBottom: Int 731 ) { 732 if (v.width > 0 && v.height > 0) { 733 countDownLatch.countDown() 734 previewView.removeOnLayoutChangeListener(this) 735 } 736 } 737 } 738 ) 739 } 740 741 @Test 742 fun meteringPointInvalid_whenPreviewViewWidthOrHeightIs0() { 743 instrumentation.runOnMainSync { 744 val cameraInfo = 745 createCameraInfo( 746 90, 747 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, 748 CameraSelector.LENS_FACING_BACK 749 ) 750 val previewView = PreviewView(context) 751 val surfaceProvider = previewView.surfaceProvider 752 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 753 val factory = previewView.meteringPointFactory 754 755 // Width and height is 0, but surface is requested, 756 // verifying the factory only creates invalid points. 757 val point = factory.createPoint(100f, 100f) 758 assertPointIsInvalid(point) 759 } 760 } 761 762 private fun assertPointIsInvalid(point: MeteringPoint) { 763 Truth.assertThat(point.x < 0f || point.x > 1.0f).isTrue() 764 Truth.assertThat(point.y < 0f || point.y > 1.0f).isTrue() 765 } 766 767 @Test 768 fun meteringPointInvalid_beforeCreatingSurfaceProvider() { 769 instrumentation.runOnMainSync { 770 val previewView = PreviewView(context) 771 // make PreviewView.getWidth() getHeight not 0. 772 setContentView(previewView) 773 val factory = previewView.meteringPointFactory 774 775 // verifying the factory only creates invalid points. 776 val point = factory.createPoint(100f, 100f) 777 assertPointIsInvalid(point) 778 } 779 } 780 781 @Test 782 fun getsImplementationMode() { 783 instrumentation.runOnMainSync { 784 val previewView = PreviewView(context) 785 previewView.implementationMode = ImplementationMode.PERFORMANCE 786 Truth.assertThat(previewView.implementationMode) 787 .isEqualTo(ImplementationMode.PERFORMANCE) 788 } 789 } 790 791 @Test 792 fun getsScaleTypeProgrammatically() { 793 instrumentation.runOnMainSync { 794 val previewView = PreviewView(context) 795 previewView.scaleType = PreviewView.ScaleType.FIT_END 796 Truth.assertThat(previewView.scaleType).isEqualTo(PreviewView.ScaleType.FIT_END) 797 } 798 } 799 800 @Test 801 fun getsScaleTypeFromXMLLayout() { 802 instrumentation.runOnMainSync { 803 val previewView = 804 LayoutInflater.from(context).inflate(R.layout.preview_view_scale_type_fit_end, null) 805 as PreviewView 806 Truth.assertThat(previewView.scaleType).isEqualTo(PreviewView.ScaleType.FIT_END) 807 } 808 } 809 810 @Test 811 fun defaultImplementationMode_isPerformance() { 812 instrumentation.runOnMainSync { 813 val previewView = PreviewView(context) 814 Truth.assertThat(previewView.implementationMode) 815 .isEqualTo(ImplementationMode.PERFORMANCE) 816 } 817 } 818 819 @Test 820 fun getsImplementationModeFromXmlLayout() { 821 instrumentation.runOnMainSync { 822 val previewView = 823 LayoutInflater.from(context) 824 .inflate(R.layout.preview_view_implementation_mode_compatible, null) 825 as PreviewView 826 Truth.assertThat(previewView.implementationMode) 827 .isEqualTo(ImplementationMode.COMPATIBLE) 828 } 829 } 830 831 @Test 832 fun redrawsPreview_whenScaleTypeChanges() { 833 instrumentation.runOnMainSync { 834 val previewView = PreviewView(context) 835 val implementation: PreviewViewImplementation = 836 Mockito.mock(TestPreviewViewImplementation::class.java) 837 previewView.mImplementation = implementation 838 previewView.scaleType = PreviewView.ScaleType.FILL_START 839 Mockito.verify(implementation, Mockito.times(1)).redrawPreview() 840 } 841 } 842 843 @Test 844 fun redrawsPreview_whenLayoutResized() { 845 val previewView = AtomicReference<PreviewView>() 846 val container = AtomicReference<FrameLayout>() 847 val implementation: PreviewViewImplementation = 848 Mockito.mock(TestPreviewViewImplementation::class.java) 849 activityScenario!!.onActivity { 850 previewView.set(PreviewView(context)) 851 previewView.get().mImplementation = implementation 852 container.set(FrameLayout(context)) 853 container.get().addView(previewView.get()) 854 setContentView(container.get()) 855 856 // Resize container in order to trigger PreviewView's onLayoutChanged listener. 857 val params = container.get().layoutParams as FrameLayout.LayoutParams 858 params.width = params.width / 2 859 container.get().requestLayout() 860 } 861 Mockito.verify(implementation, Mockito.timeout(1000).times(1)).redrawPreview() 862 } 863 864 @Test 865 fun doesNotRedrawPreview_whenDetachedFromWindow() { 866 val previewView = AtomicReference<PreviewView>() 867 val container = AtomicReference<FrameLayout>() 868 val implementation: PreviewViewImplementation = 869 Mockito.mock(TestPreviewViewImplementation::class.java) 870 activityScenario!!.onActivity { 871 previewView.set(PreviewView(context)) 872 previewView.get().mImplementation = implementation 873 container.set(FrameLayout(context)) 874 container.get().addView(previewView.get()) 875 setContentView(container.get()) 876 container.get().removeView(previewView.get()) 877 878 // Resize container 879 val params = container.get().layoutParams as FrameLayout.LayoutParams 880 params.width = params.width / 2 881 container.get().requestLayout() 882 } 883 Mockito.verify(implementation, Mockito.never()).redrawPreview() 884 } 885 886 @Test 887 fun redrawsPreview_whenReattachedToWindow() { 888 val previewView = AtomicReference<PreviewView>() 889 val container = AtomicReference<FrameLayout>() 890 val implementation: PreviewViewImplementation = 891 Mockito.mock(TestPreviewViewImplementation::class.java) 892 activityScenario!!.onActivity { 893 previewView.set(PreviewView(context)) 894 previewView.get().mImplementation = implementation 895 container.set(FrameLayout(context)) 896 container.get().addView(previewView.get()) 897 setContentView(container.get()) 898 container.get().removeView(previewView.get()) 899 container.get().addView(previewView.get()) 900 } 901 Mockito.verify(implementation, Mockito.timeout(1000).times(1)).redrawPreview() 902 } 903 904 @Test 905 fun setsDefaultBackground_whenBackgroundNotExplicitlySet() { 906 instrumentation.runOnMainSync { 907 val previewView = PreviewView(context) 908 Truth.assertThat(previewView.background).isInstanceOf(ColorDrawable::class.java) 909 val actualBackground = previewView.background as ColorDrawable 910 val expectedBackground = 911 ContextCompat.getColor(context, PreviewView.DEFAULT_BACKGROUND_COLOR) 912 Truth.assertThat(actualBackground.color).isEqualTo(expectedBackground) 913 } 914 } 915 916 @Test 917 fun overridesDefaultBackground_whenBackgroundExplicitlySet_programmatically() { 918 instrumentation.runOnMainSync { 919 val previewView = PreviewView(context) 920 val backgroundColor = ContextCompat.getColor(context, android.R.color.white) 921 previewView.setBackgroundColor(backgroundColor) 922 Truth.assertThat(previewView.background).isInstanceOf(ColorDrawable::class.java) 923 val actualBackground = previewView.background as ColorDrawable 924 Truth.assertThat(actualBackground.color).isEqualTo(backgroundColor) 925 } 926 } 927 928 @Test 929 fun overridesDefaultBackground_whenBackgroundExplicitlySet_xml() { 930 instrumentation.runOnMainSync { 931 val previewView = 932 LayoutInflater.from(context).inflate(R.layout.preview_view_background_white, null) 933 as PreviewView 934 Truth.assertThat(previewView.background).isInstanceOf(ColorDrawable::class.java) 935 val actualBackground = previewView.background as ColorDrawable 936 val expectedBackground = ContextCompat.getColor(context, android.R.color.white) 937 Truth.assertThat(actualBackground.color).isEqualTo(expectedBackground) 938 } 939 } 940 941 @Test 942 fun doNotRemovePreview_whenCreatingNewSurfaceProvider() { 943 instrumentation.runOnMainSync { 944 val previewView = PreviewView(context) 945 setContentView(previewView) 946 947 // Start a preview stream 948 val surfaceProvider = previewView.surfaceProvider 949 val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2) 950 surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo)) 951 952 // Create a new surfaceProvider 953 previewView.surfaceProvider 954 955 // Assert PreviewView doesn't remove the current preview TextureView/SurfaceView 956 var wasPreviewRemoved = true 957 for (i in 0 until previewView.childCount) { 958 if ( 959 previewView.getChildAt(i) is TextureView || 960 previewView.getChildAt(i) is SurfaceView 961 ) { 962 wasPreviewRemoved = false 963 break 964 } 965 } 966 Truth.assertThat(wasPreviewRemoved).isFalse() 967 } 968 } 969 970 @Test 971 fun canSetFrameUpdateListener() { 972 lateinit var previewView: PreviewView 973 activityScenario!!.onActivity { activity -> 974 previewView = PreviewView(context) 975 previewView.implementationMode = ImplementationMode.COMPATIBLE 976 activity.setContentView(previewView) 977 val preview = Preview.Builder().build() 978 preview.setSurfaceProvider(previewView.surfaceProvider) 979 cameraProvider!!.bindToLifecycle(activity, CameraSelector.DEFAULT_BACK_CAMERA, preview) 980 } 981 982 var executedOnExecutor = false 983 val executor = Executor { 984 it.run() 985 executedOnExecutor = true 986 } 987 988 val frameUpdateCountDownLatch = CountDownLatch(5) 989 previewView.setFrameUpdateListener(executor) { frameUpdateCountDownLatch.countDown() } 990 991 assertThat(frameUpdateCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue() 992 assertThat(executedOnExecutor).isTrue() 993 } 994 995 private fun setContentView(view: View?) { 996 activityScenario!!.onActivity { activity: FakeActivity -> activity.setContentView(view) } 997 } 998 999 private fun createSurfaceRequest( 1000 cameraInfo: CameraInfoInternal, 1001 ): SurfaceRequest { 1002 val fakeCamera = FakeCamera(/* cameraControl= */ null, cameraInfo) 1003 val surfaceRequest = SurfaceRequest(DEFAULT_SURFACE_SIZE, fakeCamera) {} 1004 surfaceRequestList.add(surfaceRequest) 1005 return surfaceRequest 1006 } 1007 1008 private fun createCameraInfo(implementationType: String): CameraInfoInternal { 1009 val cameraInfoInternal = FakeCameraInfoInternal() 1010 cameraInfoInternal.implementationType = implementationType 1011 return cameraInfoInternal 1012 } 1013 1014 private fun createCameraInfo( 1015 rotationDegrees: Int, 1016 implementationType: String, 1017 @CameraSelector.LensFacing lensFacing: Int 1018 ): CameraInfoInternal { 1019 val cameraInfoInternal = FakeCameraInfoInternal(rotationDegrees, lensFacing) 1020 cameraInfoInternal.implementationType = implementationType 1021 return cameraInfoInternal 1022 } 1023 1024 private fun updateCropRectAndWaitForIdle(cropRect: Rect) { 1025 for (surfaceRequest in surfaceRequestList) { 1026 surfaceRequest.updateTransformationInfo( 1027 SurfaceRequest.TransformationInfo.of( 1028 cropRect, 1029 0, 1030 Surface.ROTATION_0, 1031 /*hasCameraTransform=*/ true, 1032 /*sensorToBufferTransform=*/ Matrix(), 1033 /*mirroring=*/ false 1034 ) 1035 ) 1036 } 1037 instrumentation.waitForIdleSync() 1038 } 1039 1040 private fun hasSurfaceViewQuirk(): Boolean { 1041 return DeviceQuirks.get(SurfaceViewStretchedQuirk::class.java) != null || 1042 DeviceQuirks.get(SurfaceViewNotCroppedByParentQuirk::class.java) != null 1043 } 1044 1045 /** 1046 * An empty implementation of [PreviewViewImplementation] used for testing. It allows mocking 1047 * [PreviewViewImplementation] since the latter is package private. 1048 */ 1049 internal open class TestPreviewViewImplementation 1050 constructor(parent: FrameLayout, previewTransform: PreviewTransformation) : 1051 PreviewViewImplementation(parent, previewTransform) { 1052 override fun initializePreview() {} 1053 1054 override fun getPreview(): View? { 1055 return null 1056 } 1057 1058 override fun onSurfaceRequested( 1059 surfaceRequest: SurfaceRequest, 1060 onSurfaceNotInUseListener: OnSurfaceNotInUseListener? 1061 ) {} 1062 1063 public override fun redrawPreview() {} 1064 1065 public override fun onAttachedToWindow() {} 1066 1067 public override fun onDetachedFromWindow() {} 1068 1069 public override fun waitForNextFrame(): ListenableFuture<Void> { 1070 return Futures.immediateFuture(null) 1071 } 1072 1073 public override fun getPreviewBitmap(): Bitmap? { 1074 return null 1075 } 1076 } 1077 1078 companion object { 1079 private val DEFAULT_SURFACE_SIZE: Size by lazy { Size(640, 480) } 1080 private val DEFAULT_CROP_RECT = Rect(0, 0, 640, 480) 1081 private val SMALLER_CROP_RECT = Rect(20, 20, 600, 400) 1082 private const val TIMEOUT_SECONDS = 10 1083 1084 @JvmStatic 1085 @Parameterized.Parameters(name = "{0}") 1086 fun data() = 1087 listOf( 1088 arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()), 1089 arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig()) 1090 ) 1091 } 1092 } 1093