1 /* 2 * 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.camera2.internal; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 import static org.junit.Assume.assumeFalse; 24 import static org.junit.Assume.assumeTrue; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.reset; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 30 import android.content.Context; 31 import android.graphics.Rect; 32 import android.hardware.camera2.CameraAccessException; 33 import android.hardware.camera2.CameraCharacteristics; 34 import android.hardware.camera2.CaptureRequest; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 38 import androidx.camera.camera2.Camera2Config; 39 import androidx.camera.camera2.impl.Camera2ImplConfig; 40 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat; 41 import androidx.camera.core.CameraInfoUnavailableException; 42 import androidx.camera.core.CameraSelector; 43 import androidx.camera.core.CameraXConfig; 44 import androidx.camera.core.ZoomState; 45 import androidx.camera.core.impl.CameraControlInternal.ControlUpdateCallback; 46 import androidx.camera.core.impl.SessionConfig; 47 import androidx.camera.core.impl.utils.executor.CameraXExecutors; 48 import androidx.camera.testing.impl.CameraUtil; 49 import androidx.camera.testing.impl.CameraXUtil; 50 import androidx.camera.testing.impl.HandlerUtil; 51 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner; 52 import androidx.core.os.HandlerCompat; 53 import androidx.test.annotation.UiThreadTest; 54 import androidx.test.core.app.ApplicationProvider; 55 import androidx.test.ext.junit.runners.AndroidJUnit4; 56 import androidx.test.filters.SdkSuppress; 57 import androidx.test.filters.SmallTest; 58 59 import com.google.common.util.concurrent.ListenableFuture; 60 61 import org.jspecify.annotations.NonNull; 62 import org.junit.After; 63 import org.junit.Assert; 64 import org.junit.Before; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 import org.mockito.Mockito; 68 69 import java.util.Objects; 70 import java.util.concurrent.CountDownLatch; 71 import java.util.concurrent.ExecutionException; 72 import java.util.concurrent.ScheduledExecutorService; 73 import java.util.concurrent.TimeUnit; 74 import java.util.concurrent.TimeoutException; 75 76 @SmallTest 77 @RunWith(AndroidJUnit4.class) 78 @SdkSuppress(minSdkVersion = 21) 79 @SuppressWarnings("ConstantConditions") // We might hit an NPE, which is fine. It's a test. 80 public final class ZoomControlDeviceTest { 81 private static final int TOLERANCE = 5; 82 private ZoomControl mZoomControl; 83 private Camera2CameraControlImpl mCamera2CameraControlImpl; 84 private HandlerThread mHandlerThread; 85 private ControlUpdateCallback mControlUpdateCallback; 86 private CameraCharacteristics mCameraCharacteristics; 87 private CameraCharacteristicsCompat mCameraCharacteristicsCompat; 88 private Handler mHandler; 89 90 @Before setUp()91 public void setUp() 92 throws CameraInfoUnavailableException, CameraAccessException, InterruptedException { 93 assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)); 94 95 // Init CameraX 96 Context context = ApplicationProvider.getApplicationContext(); 97 CameraXConfig config = Camera2Config.defaultConfig(); 98 CameraXUtil.initialize(context, config); 99 100 mCameraCharacteristics = 101 CameraUtil.getCameraCharacteristics(CameraSelector.LENS_FACING_BACK); 102 103 mControlUpdateCallback = mock(ControlUpdateCallback.class); 104 mHandlerThread = new HandlerThread("ControlThread"); 105 mHandlerThread.start(); 106 mHandler = HandlerCompat.createAsync(mHandlerThread.getLooper()); 107 108 ScheduledExecutorService executorService = CameraXExecutors.newHandlerExecutor(mHandler); 109 String cameraId = CameraUtil.getCameraIdWithLensFacing(CameraSelector.LENS_FACING_BACK); 110 mCameraCharacteristicsCompat = CameraCharacteristicsCompat.toCameraCharacteristicsCompat( 111 mCameraCharacteristics, cameraId); 112 assumeTrue(getMaxDigitalZoom() >= 2.0); 113 114 mCamera2CameraControlImpl = new Camera2CameraControlImpl(mCameraCharacteristicsCompat, 115 executorService, executorService, mControlUpdateCallback); 116 117 mZoomControl = mCamera2CameraControlImpl.getZoomControl(); 118 mZoomControl.setActive(true); 119 120 // Await Camera2CameraControlImpl updateSessionConfig to complete. 121 HandlerUtil.waitForLooperToIdle(mHandler); 122 Mockito.reset(mControlUpdateCallback); 123 } 124 125 @After tearDown()126 public void tearDown() throws ExecutionException, InterruptedException, TimeoutException { 127 CameraXUtil.shutdown().get(10000, TimeUnit.MILLISECONDS); 128 if (mHandlerThread != null) { 129 mHandlerThread.quit(); 130 } 131 } 132 isAndroidRZoomEnabled()133 private boolean isAndroidRZoomEnabled() { 134 return ZoomControl.isAndroidRZoomSupported(mCameraCharacteristicsCompat); 135 } 136 137 @Test 138 @UiThreadTest setZoomRatio_getValueIsCorrect_InUIThread()139 public void setZoomRatio_getValueIsCorrect_InUIThread() { 140 final float newZoomRatio = 2.0f; 141 assumeTrue(newZoomRatio <= mZoomControl.getZoomState().getValue().getMaxZoomRatio()); 142 143 // We can only ensure new value is reflected immediately on getZoomRatio on UI thread 144 // because of the nature of LiveData. 145 mZoomControl.setZoomRatio(newZoomRatio); 146 assertThat(mZoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(newZoomRatio); 147 } 148 149 @Test 150 @UiThreadTest setZoomRatio_largerThanMax_zoomUnmodified()151 public void setZoomRatio_largerThanMax_zoomUnmodified() { 152 assumeTrue(2.0f <= mZoomControl.getZoomState().getValue().getMaxZoomRatio()); 153 mZoomControl.setZoomRatio(2.0f); 154 float maxZoomRatio = mZoomControl.getZoomState().getValue().getMaxZoomRatio(); 155 mZoomControl.setZoomRatio(maxZoomRatio + 1.0f); 156 assertThat(mZoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(2.0f); 157 } 158 159 @Test setZoomRatio_largerThanMax_OutOfRangeException()160 public void setZoomRatio_largerThanMax_OutOfRangeException() { 161 float maxZoomRatio = mZoomControl.getZoomState().getValue().getMaxZoomRatio(); 162 ListenableFuture<Void> result = mZoomControl.setZoomRatio(maxZoomRatio + 1.0f); 163 164 assertThrowOutOfRangeExceptionOnListenableFuture(result); 165 } 166 assertThrowOutOfRangeExceptionOnListenableFuture(ListenableFuture<Void> result)167 private void assertThrowOutOfRangeExceptionOnListenableFuture(ListenableFuture<Void> result) { 168 try { 169 result.get(100, TimeUnit.MILLISECONDS); 170 } catch (InterruptedException | TimeoutException ignored) { 171 } catch (ExecutionException ee) { 172 assertThat(ee.getCause()).isInstanceOf(IllegalArgumentException.class); 173 return; 174 } 175 176 fail(); 177 } 178 179 @Test 180 @UiThreadTest setZoomRatio_smallerThanMin_zoomUnmodified()181 public void setZoomRatio_smallerThanMin_zoomUnmodified() { 182 assumeTrue(2.0f <= mZoomControl.getZoomState().getValue().getMaxZoomRatio()); 183 mZoomControl.setZoomRatio(2.0f); 184 float minZoomRatio = mZoomControl.getZoomState().getValue().getMinZoomRatio(); 185 mZoomControl.setZoomRatio(minZoomRatio - 1.0f); 186 assertThat(mZoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(2.0f); 187 } 188 189 @Test setZoomRatio_smallerThanMin_OutOfRangeException()190 public void setZoomRatio_smallerThanMin_OutOfRangeException() { 191 float minZoomRatio = mZoomControl.getZoomState().getValue().getMinZoomRatio(); 192 ListenableFuture<Void> result = mZoomControl.setZoomRatio(minZoomRatio - 1.0f); 193 assertThrowOutOfRangeExceptionOnListenableFuture(result); 194 } 195 getSensorRect()196 private Rect getSensorRect() { 197 Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 198 // Some device like pixel 2 will have (0, 8) as the left-top corner. 199 return new Rect(0, 0, rect.width(), rect.height()); 200 } 201 202 @Test setZoomRatioBy1_0_isEqualToSensorRect()203 public void setZoomRatioBy1_0_isEqualToSensorRect() throws InterruptedException { 204 assumeFalse(isAndroidRZoomEnabled()); 205 mZoomControl.setZoomRatio(1.0f); 206 HandlerUtil.waitForLooperToIdle(mHandler); 207 Rect sessionCropRegion = getSessionCropRegion(mControlUpdateCallback); 208 assertThat(sessionCropRegion).isEqualTo(getSensorRect()); 209 } 210 211 @Test 212 @SdkSuppress(minSdkVersion = 30) setZoomRatioBy1_0_androidRZoomRatioIsUpdated()213 public void setZoomRatioBy1_0_androidRZoomRatioIsUpdated() throws InterruptedException { 214 assumeTrue(isAndroidRZoomEnabled()); 215 mZoomControl.setZoomRatio(1.0f); 216 HandlerUtil.waitForLooperToIdle(mHandler); 217 float zoomRatio = getAndroidRZoomRatio(mControlUpdateCallback); 218 assertThat(zoomRatio).isEqualTo(1.0f); 219 } 220 221 @Test setZoomRatioBy2_0_cropRegionIsSetCorrectly()222 public void setZoomRatioBy2_0_cropRegionIsSetCorrectly() throws InterruptedException { 223 assumeFalse(isAndroidRZoomEnabled()); 224 mZoomControl.setZoomRatio(2.0f); 225 HandlerUtil.waitForLooperToIdle(mHandler); 226 227 Rect sessionCropRegion = getSessionCropRegion(mControlUpdateCallback); 228 229 Rect sensorRect = getSensorRect(); 230 int cropX = (sensorRect.width() / 4); 231 int cropY = (sensorRect.height() / 4); 232 Rect cropRect = new Rect(cropX, cropY, cropX + sensorRect.width() / 2, 233 cropY + sensorRect.height() / 2); 234 assertThat(sessionCropRegion).isEqualTo(cropRect); 235 } 236 237 @Test 238 @SdkSuppress(minSdkVersion = 30) setZoomRatioBy2_0_androidRZoomRatioIsUpdated()239 public void setZoomRatioBy2_0_androidRZoomRatioIsUpdated() throws InterruptedException { 240 assumeTrue(isAndroidRZoomEnabled()); 241 mZoomControl.setZoomRatio(2.0f); 242 HandlerUtil.waitForLooperToIdle(mHandler); 243 244 float zoomRatio = getAndroidRZoomRatio(mControlUpdateCallback); 245 assertThat(zoomRatio).isEqualTo(2.0f); 246 } 247 getSessionCropRegion(ControlUpdateCallback controlUpdateCallback)248 private @NonNull Rect getSessionCropRegion(ControlUpdateCallback controlUpdateCallback) { 249 verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); 250 SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); 251 Camera2ImplConfig camera2Config = new Camera2ImplConfig( 252 sessionConfig.getImplementationOptions()); 253 254 reset(controlUpdateCallback); 255 return Objects.requireNonNull(camera2Config.getCaptureRequestOption( 256 CaptureRequest.SCALER_CROP_REGION, null)); 257 } 258 259 @SdkSuppress(minSdkVersion = 30) getAndroidRZoomRatio(ControlUpdateCallback controlUpdateCallback)260 private @NonNull Float getAndroidRZoomRatio(ControlUpdateCallback controlUpdateCallback) { 261 verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); 262 SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); 263 Camera2ImplConfig camera2Config = new Camera2ImplConfig( 264 sessionConfig.getImplementationOptions()); 265 266 reset(controlUpdateCallback); 267 assertThat(camera2Config.getCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, null)) 268 .isNull(); 269 return Objects.requireNonNull(camera2Config.getCaptureRequestOption( 270 CaptureRequest.CONTROL_ZOOM_RATIO, null)); 271 } 272 273 @UiThreadTest 274 @Test setLinearZoomBy0_isSameAsMinRatio()275 public void setLinearZoomBy0_isSameAsMinRatio() { 276 mZoomControl.setLinearZoom(0); 277 float ratioAtPercentage0 = mZoomControl.getZoomState().getValue().getZoomRatio(); 278 279 mZoomControl.setZoomRatio(mZoomControl.getZoomState().getValue().getMinZoomRatio()); 280 float ratioAtMinZoomRatio = mZoomControl.getZoomState().getValue().getZoomRatio(); 281 282 assertThat(ratioAtPercentage0).isEqualTo(ratioAtMinZoomRatio); 283 } 284 285 @UiThreadTest 286 @Test setLinearZoomBy1_isSameAsMaxRatio()287 public void setLinearZoomBy1_isSameAsMaxRatio() { 288 mZoomControl.setLinearZoom(1); 289 float ratioAtPercentage1 = mZoomControl.getZoomState().getValue().getZoomRatio(); 290 291 mZoomControl.setZoomRatio(mZoomControl.getZoomState().getValue().getMaxZoomRatio()); 292 float ratioAtMaxZoomRatio = mZoomControl.getZoomState().getValue().getZoomRatio(); 293 294 assertThat(ratioAtPercentage1).isEqualTo(ratioAtMaxZoomRatio); 295 } 296 297 @UiThreadTest 298 @Test setLinearZoomBy0_5_isHalfCropWidth()299 public void setLinearZoomBy0_5_isHalfCropWidth() throws InterruptedException { 300 assumeFalse(isAndroidRZoomEnabled()); 301 302 mZoomControl.setLinearZoom(1f); 303 HandlerUtil.waitForLooperToIdle(mHandler); 304 Rect cropRegionMaxZoom = getSessionCropRegion(mControlUpdateCallback); 305 306 Rect cropRegionMinZoom = getSensorRect(); 307 308 mZoomControl.setLinearZoom(0.5f); 309 HandlerUtil.waitForLooperToIdle(mHandler); 310 Rect cropRegionHalfZoom = getSessionCropRegion(mControlUpdateCallback); 311 312 Assert.assertEquals(cropRegionHalfZoom.width(), 313 (cropRegionMinZoom.width() + cropRegionMaxZoom.width()) / 2.0f, TOLERANCE); 314 } 315 316 @UiThreadTest 317 @Test 318 @SdkSuppress(minSdkVersion = 30) setLinearZoomBy0_5_androidRZoomRatioUpdatedCorrectly()319 public void setLinearZoomBy0_5_androidRZoomRatioUpdatedCorrectly() throws InterruptedException { 320 assumeTrue(isAndroidRZoomEnabled()); 321 322 mZoomControl.setLinearZoom(1f); 323 HandlerUtil.waitForLooperToIdle(mHandler); 324 float zoomRatioForLinearMax = getAndroidRZoomRatio(mControlUpdateCallback); 325 final float cropWidth = 10000f; 326 float cropWidthForLinearMax = cropWidth / zoomRatioForLinearMax; 327 328 mZoomControl.setLinearZoom(0f); 329 HandlerUtil.waitForLooperToIdle(mHandler); 330 float zoomRatioForLinearMin = getAndroidRZoomRatio(mControlUpdateCallback); 331 float cropWidthForLinearMin = cropWidth / zoomRatioForLinearMin; 332 333 mZoomControl.setLinearZoom(0.5f); 334 HandlerUtil.waitForLooperToIdle(mHandler); 335 float zoomRatioForLinearHalf = getAndroidRZoomRatio(mControlUpdateCallback); 336 float cropWidthForLinearHalf = cropWidth / zoomRatioForLinearHalf; 337 338 Assert.assertEquals(cropWidthForLinearHalf, 339 (cropWidthForLinearMin + cropWidthForLinearMax) / 2.0f, TOLERANCE); 340 } 341 342 @UiThreadTest 343 @Test setLinearZoom_cropWidthChangedLinearly()344 public void setLinearZoom_cropWidthChangedLinearly() throws InterruptedException { 345 assumeFalse(isAndroidRZoomEnabled()); 346 347 // crop region in percentage == 0 is null, need to use sensor rect instead. 348 Rect prevCropRegion = getSensorRect(); 349 350 float prevWidthDelta = 0; 351 for (float percentage = 0.1f; percentage < 1.0f; percentage += 0.1f) { 352 353 mZoomControl.setLinearZoom(percentage); 354 HandlerUtil.waitForLooperToIdle(mHandler); 355 Rect cropRegion = getSessionCropRegion(mControlUpdateCallback); 356 357 if (prevWidthDelta == 0) { 358 prevWidthDelta = prevCropRegion.width() - cropRegion.width(); 359 } else { 360 float widthDelta = prevCropRegion.width() - cropRegion.width(); 361 Assert.assertEquals(prevWidthDelta, widthDelta, TOLERANCE); 362 } 363 364 prevCropRegion = cropRegion; 365 } 366 } 367 368 @UiThreadTest 369 @Test 370 @SdkSuppress(minSdkVersion = 30) setLinearZoom_androidRZoomRatio_cropWidthChangedLinearly()371 public void setLinearZoom_androidRZoomRatio_cropWidthChangedLinearly() 372 throws InterruptedException { 373 assumeTrue(isAndroidRZoomEnabled()); 374 final float cropWidth = 10000; 375 376 mZoomControl.setLinearZoom(0f); 377 HandlerUtil.waitForLooperToIdle(mHandler); 378 float zoomRatioForLinearMin = getAndroidRZoomRatio(mControlUpdateCallback); 379 380 float prevCropWidth = cropWidth / zoomRatioForLinearMin; 381 382 float prevWidthDelta = 0; 383 for (float percentage = 0.1f; percentage < 1.0f; percentage += 0.1f) { 384 385 mZoomControl.setLinearZoom(percentage); 386 HandlerUtil.waitForLooperToIdle(mHandler); 387 float zoomRatio = getAndroidRZoomRatio(mControlUpdateCallback); 388 float cropWidthForTheRatio = cropWidth / zoomRatio; 389 390 if (prevWidthDelta == 0) { 391 prevWidthDelta = prevCropWidth - cropWidthForTheRatio; 392 } else { 393 float widthDelta = prevCropWidth - cropWidthForTheRatio; 394 Assert.assertEquals(prevWidthDelta, widthDelta, TOLERANCE); 395 } 396 397 prevCropWidth = cropWidthForTheRatio; 398 } 399 } 400 401 @UiThreadTest 402 @Test setLinearZoom_largerThan1_zoomUnmodified()403 public void setLinearZoom_largerThan1_zoomUnmodified() { 404 mZoomControl.setLinearZoom(0.5f); 405 mZoomControl.setLinearZoom(1.1f); 406 assertThat(mZoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.5f); 407 } 408 409 @Test setLinearZoom_largerThan1_outOfRangeException()410 public void setLinearZoom_largerThan1_outOfRangeException() { 411 ListenableFuture<Void> result = mZoomControl.setLinearZoom(1.1f); 412 assertThrowOutOfRangeExceptionOnListenableFuture(result); 413 } 414 415 @UiThreadTest 416 @Test setLinearZoom_smallerThan0_zoomUnmodified()417 public void setLinearZoom_smallerThan0_zoomUnmodified() { 418 mZoomControl.setLinearZoom(0.5f); 419 mZoomControl.setLinearZoom(-0.1f); 420 assertThat(mZoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.5f); 421 } 422 423 @Test setLinearZoom_smallerThan0_outOfRangeException()424 public void setLinearZoom_smallerThan0_outOfRangeException() { 425 ListenableFuture<Void> result = mZoomControl.setLinearZoom(-0.1f); 426 assertThrowOutOfRangeExceptionOnListenableFuture(result); 427 } 428 429 @UiThreadTest 430 @Test getterLiveData_defaultValueIsNonNull()431 public void getterLiveData_defaultValueIsNonNull() { 432 assertThat(mZoomControl.getZoomState().getValue()).isNotNull(); 433 } 434 435 @UiThreadTest 436 @Test getZoomRatioLiveData_observerIsCalledWhenZoomRatioIsSet()437 public void getZoomRatioLiveData_observerIsCalledWhenZoomRatioIsSet() 438 throws InterruptedException { 439 CountDownLatch latch1 = new CountDownLatch(1); 440 CountDownLatch latch2 = new CountDownLatch(1); 441 CountDownLatch latch3 = new CountDownLatch(1); 442 FakeLifecycleOwner lifecycleOwner = new FakeLifecycleOwner(); 443 lifecycleOwner.startAndResume(); 444 445 mZoomControl.getZoomState().observe(lifecycleOwner, (value) -> { 446 if (value.getZoomRatio() == 1.2f) { 447 latch1.countDown(); 448 } else if (value.getZoomRatio() == 1.5f) { 449 latch2.countDown(); 450 } else if (value.getZoomRatio() == 2.0f) { 451 latch3.countDown(); 452 } 453 }); 454 455 mZoomControl.setZoomRatio(1.2f); 456 mZoomControl.setZoomRatio(1.5f); 457 mZoomControl.setZoomRatio(2.0f); 458 459 assertTrue(latch1.await(500, TimeUnit.MILLISECONDS)); 460 assertTrue(latch2.await(500, TimeUnit.MILLISECONDS)); 461 assertTrue(latch3.await(500, TimeUnit.MILLISECONDS)); 462 } 463 464 @UiThreadTest 465 @Test getZoomRatioLiveData_observerIsCalledWhenZoomPercentageIsSet()466 public void getZoomRatioLiveData_observerIsCalledWhenZoomPercentageIsSet() 467 throws InterruptedException { 468 CountDownLatch latch = new CountDownLatch(3); 469 FakeLifecycleOwner lifecycleOwner = new FakeLifecycleOwner(); 470 lifecycleOwner.startAndResume(); 471 472 mZoomControl.getZoomState().observe(lifecycleOwner, (value) -> { 473 if (value.getZoomRatio() != 1.0f) { 474 latch.countDown(); 475 } 476 }); 477 478 mZoomControl.setLinearZoom(0.1f); 479 mZoomControl.setLinearZoom(0.2f); 480 mZoomControl.setLinearZoom(0.3f); 481 482 assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); 483 } 484 485 @UiThreadTest 486 @Test getZoomPercentageLiveData_observerIsCalledWhenZoomPercentageIsSet()487 public void getZoomPercentageLiveData_observerIsCalledWhenZoomPercentageIsSet() 488 throws InterruptedException { 489 CountDownLatch latch1 = new CountDownLatch(1); 490 CountDownLatch latch2 = new CountDownLatch(1); 491 CountDownLatch latch3 = new CountDownLatch(1); 492 FakeLifecycleOwner lifecycleOwner = new FakeLifecycleOwner(); 493 lifecycleOwner.startAndResume(); 494 495 mZoomControl.getZoomState().observe(lifecycleOwner, (value) -> { 496 if (value.getLinearZoom() == 0.1f) { 497 latch1.countDown(); 498 } else if (value.getLinearZoom() == 0.2f) { 499 latch2.countDown(); 500 } else if (value.getLinearZoom() == 0.3f) { 501 latch3.countDown(); 502 } 503 }); 504 505 mZoomControl.setLinearZoom(0.1f); 506 mZoomControl.setLinearZoom(0.2f); 507 mZoomControl.setLinearZoom(0.3f); 508 509 assertTrue(latch1.await(500, TimeUnit.MILLISECONDS)); 510 assertTrue(latch2.await(500, TimeUnit.MILLISECONDS)); 511 assertTrue(latch3.await(500, TimeUnit.MILLISECONDS)); 512 } 513 514 @UiThreadTest 515 @Test getZoomPercentageLiveData_observerIsCalledWhenZoomRatioIsSet()516 public void getZoomPercentageLiveData_observerIsCalledWhenZoomRatioIsSet() 517 throws InterruptedException { 518 CountDownLatch latch = new CountDownLatch(3); 519 FakeLifecycleOwner lifecycleOwner = new FakeLifecycleOwner(); 520 lifecycleOwner.startAndResume(); 521 522 mZoomControl.getZoomState().observe(lifecycleOwner, (value) -> { 523 if (value.getLinearZoom() != 0f) { 524 latch.countDown(); 525 } 526 }); 527 528 mZoomControl.setZoomRatio(1.2f); 529 mZoomControl.setZoomRatio(1.5f); 530 mZoomControl.setZoomRatio(2.0f); 531 532 assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); 533 } 534 535 @UiThreadTest 536 @Test getZoomRatioDefaultValue()537 public void getZoomRatioDefaultValue() { 538 assertThat(mZoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo( 539 ZoomControl.DEFAULT_ZOOM_RATIO); 540 } 541 542 @UiThreadTest 543 @Test getZoomPercentageDefaultValue()544 public void getZoomPercentageDefaultValue() { 545 assumeFalse(isAndroidRZoomEnabled()); 546 assertThat(mZoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0); 547 } 548 549 @UiThreadTest 550 @Test getMaxZoomRatio_isMaxDigitalZoom()551 public void getMaxZoomRatio_isMaxDigitalZoom() { 552 float maxZoom = mZoomControl.getZoomState().getValue().getMaxZoomRatio(); 553 assertThat(maxZoom).isEqualTo(getMaxDigitalZoom()); 554 } 555 556 @UiThreadTest 557 @Test getMinZoomRatio_isOne()558 public void getMinZoomRatio_isOne() { 559 assumeFalse(isAndroidRZoomEnabled()); 560 float minZoom = mZoomControl.getZoomState().getValue().getMinZoomRatio(); 561 assertThat(minZoom).isEqualTo(1f); 562 } 563 getMaxDigitalZoom()564 private float getMaxDigitalZoom() { 565 if (isAndroidRZoomEnabled()) { 566 return mCameraCharacteristics.get( 567 CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper(); 568 } 569 return mCameraCharacteristics.get( 570 CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); 571 } 572 573 @Test getMaxZoomRatio_isEqualToMaxDigitalZoom()574 public void getMaxZoomRatio_isEqualToMaxDigitalZoom() { 575 float maxZoom = mZoomControl.getZoomState().getValue().getMaxZoomRatio(); 576 577 assertThat(maxZoom).isEqualTo(getMaxDigitalZoom()); 578 } 579 580 @UiThreadTest 581 @Test valueIsResetAfterInactive()582 public void valueIsResetAfterInactive() { 583 mZoomControl.setActive(true); 584 mZoomControl.setLinearZoom(0.2f); // this will change ratio and percentage. 585 586 mZoomControl.setActive(false); 587 588 assertThat(mZoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo( 589 ZoomControl.DEFAULT_ZOOM_RATIO); 590 } 591 592 @Test maxZoomShouldBeLargerThanOrEqualToMinZoom()593 public void maxZoomShouldBeLargerThanOrEqualToMinZoom() { 594 ZoomState zoomState = mZoomControl.getZoomState().getValue(); 595 assertThat(zoomState.getMaxZoomRatio()).isAtLeast(zoomState.getMinZoomRatio()); 596 } 597 } 598