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