Merge 502eef783e into b9a5e4eb9e
	
		
	
				
					
				
			
						commit
						6b3e587e4b
					
				| @ -0,0 +1 @@ | ||||
| Contributing guidelines are [hosted here](https://natario1.github.io/CameraView/extra/contributing). | ||||
| @ -0,0 +1,12 @@ | ||||
| # These are supported funding model platforms | ||||
| 
 | ||||
| github: [natario1] | ||||
| patreon: # Replace with a single Patreon username | ||||
| open_collective: cameraview | ||||
| ko_fi: # Replace with a single Ko-fi username | ||||
| tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||||
| community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||||
| liberapay: # Replace with a single Liberapay username | ||||
| issuehunt: # Replace with a single IssueHunt username | ||||
| otechie: # Replace with a single Otechie username | ||||
| custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | ||||
| @ -1,10 +1,14 @@ | ||||
| ### Before you go | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. | ||||
| If the edited files were covered by tests, updated tests are required for merging. Please look into the tests folders and make sure you cover new code. | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that | ||||
| we can discuss the best approach to address the problem. Without a reference issue and discussion,  | ||||
| unfortunately, this PR will likely be ignored. | ||||
| 
 | ||||
| If the edited files were covered by tests, updated tests are required for merging.  | ||||
| Please look into the tests folders and make sure you cover new code. | ||||
| 
 | ||||
| - Fixes ... (*issue number*) | ||||
| - Tests: ... (*yes/no*) | ||||
| - Docs updated: ... (*yes/no*) | ||||
| 
 | ||||
| ### Solution | ||||
| If applicable, briefly describe how the issue was addressed. | ||||
| If applicable, describe briefly how the issue was addressed. | ||||
|  | ||||
| @ -0,0 +1,113 @@ | ||||
| # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions | ||||
| # Renaming ? Change the README badge. | ||||
| name: Build | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - master | ||||
|       - main | ||||
|   pull_request: | ||||
| jobs: | ||||
|   ANDROID_BASE_CHECKS: | ||||
|     name: Base Checks | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Perform base checks | ||||
|         run: ./gradlew demo:assembleDebug cameraview:publishToDirectory --stacktrace | ||||
|   ANDROID_UNIT_TESTS: | ||||
|     name: Unit Tests | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Execute unit tests | ||||
|         run: ./gradlew cameraview:runUnitTests --stacktrace | ||||
|       - name: Upload unit tests artifact | ||||
|         uses: actions/upload-artifact@v1 | ||||
|         with: | ||||
|           name: unit_tests | ||||
|           path: ./cameraview/build/coverage_input/unit_tests | ||||
|   ANDROID_EMULATOR_TESTS: | ||||
|     name: Emulator Tests | ||||
|     runs-on: macOS-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         # TODO 29 fails due to Mockito issues, probably reproducible locally | ||||
|         # 22-28 work (some of them, with SdkExclude restrictions) | ||||
|         EMULATOR_API: [22, 23, 24, 25, 26, 27, 28] | ||||
|         include: | ||||
|           - EMULATOR_API: 28 | ||||
|             EMULATOR_ARCH: x86_64 | ||||
|           - EMULATOR_API: 27 | ||||
|             EMULATOR_ARCH: x86_64 | ||||
|           - EMULATOR_API: 26 | ||||
|             EMULATOR_ARCH: x86_64 | ||||
|           - EMULATOR_API: 25 | ||||
|             EMULATOR_ARCH: x86 | ||||
|           - EMULATOR_API: 24 | ||||
|             EMULATOR_ARCH: x86 | ||||
|           - EMULATOR_API: 23 | ||||
|             EMULATOR_ARCH: x86 | ||||
|           - EMULATOR_API: 22 | ||||
|             EMULATOR_ARCH: x86 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Execute emulator tests | ||||
|         timeout-minutes: 30 | ||||
|         uses: reactivecircus/android-emulator-runner@v2.2.0 | ||||
|         with: | ||||
|           api-level: ${{ matrix.EMULATOR_API }} | ||||
|           arch: ${{ matrix.EMULATOR_ARCH }} | ||||
|           disable-animations: true | ||||
|           profile: Nexus 5X | ||||
|           emulator-options: -no-snapshot -no-window -no-boot-anim -camera-back emulated -camera-front emulated -gpu swiftshader_indirect | ||||
|           emulator-build: 6110076 | ||||
|           script: ./.github/workflows/emulator_script.sh | ||||
|       - name: Upload emulator tests artifact | ||||
|         uses: actions/upload-artifact@v1 | ||||
|         with: | ||||
|           name: emulator_tests_${{ matrix.EMULATOR_API }} | ||||
|           path: ./cameraview/build/coverage_input/android_tests | ||||
|   CODE_COVERAGE: | ||||
|     name: Code Coverage Report | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [ANDROID_UNIT_TESTS, ANDROID_EMULATOR_TESTS] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Download unit tests artifact | ||||
|         uses: actions/download-artifact@v1 | ||||
|         with: | ||||
|           name: unit_tests | ||||
|           path: ./cameraview/build/coverage_input/unit_tests | ||||
|       - name: Download emulator tests artifact | ||||
|         uses: actions/download-artifact@v1 | ||||
|         with: | ||||
|           # 27 is the EMULATOR_API with less SdkExclude annotations, and should have | ||||
|           # the best possible coverage. | ||||
|           name: emulator_tests_27 | ||||
|           path: ./cameraview/build/coverage_input/android_tests | ||||
|       - name: Create merged coverage report | ||||
|         run: ./gradlew cameraview:computeCoverage | ||||
|       - name: Upload merged coverage report (GitHub) | ||||
|         uses: actions/upload-artifact@v1 | ||||
|         with: | ||||
|           name: report | ||||
|           path: ./cameraview/build/coverage_output/xml | ||||
|       - name: Upload merged coverage report (Codecov) | ||||
|         uses: codecov/codecov-action@v1 | ||||
|         with: | ||||
|           file: ./cameraview/build/coverage_output/xml/* | ||||
|           fail_ci_if_error: true | ||||
| @ -0,0 +1,20 @@ | ||||
| # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions | ||||
| name: Deploy | ||||
| on: | ||||
|   release: | ||||
|     types: [published] | ||||
| jobs: | ||||
|   BINTRAY_UPLOAD: | ||||
|     name: Bintray Upload | ||||
|     runs-on: ubuntu-latest | ||||
|     env: | ||||
|       BINTRAY_USER: ${{ secrets.BINTRAY_USER }} | ||||
|       BINTRAY_KEY: ${{ secrets.BINTRAY_KEY }} | ||||
|       BINTRAY_REPO: ${{ secrets.BINTRAY_REPO }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: 1.8 | ||||
|       - name: Perform bintray upload | ||||
|         run: ./gradlew cameraview:publishToBintray | ||||
| @ -0,0 +1,14 @@ | ||||
| #!/usr/bin/env bash | ||||
| # Core | ||||
| ADB_TAGS="CameraView:I CameraCallbacks:I CameraOrchestrator:I CameraEngine:I" | ||||
| ADB_TAGS="$ADB_TAGS CameraUtils:I WorkerHandler:I" | ||||
| # Recorders | ||||
| ADB_TAGS="$ADB_TAGS VideoRecorder:I FullVideoRecorder:I SnapshotVideoRecorder:I" | ||||
| ADB_TAGS="$ADB_TAGS FullPictureRecorder:I SnapshotPictureRecorder:I DeviceEncoders:I" | ||||
| # Video encoders | ||||
| ADB_TAGS="$ADB_TAGS MediaEncoderEngine:I MediaEncoder:I AudioMediaEncoder:I VideoMediaEncoder:I TextureMediaEncoder:I" | ||||
| # Debugging | ||||
| ADB_TAGS="$ADB_TAGS CameraIntegrationTest:I MessageQueue:W MPEG4Writer:I" | ||||
| adb logcat -c | ||||
| adb logcat $ADB_TAGS *:E -v color & | ||||
| ./gradlew cameraview:runAndroidTests | ||||
| @ -1,86 +0,0 @@ | ||||
| # https://github.com/andstatus/andstatus/blob/master/.travis.yml | ||||
| 
 | ||||
| language: android | ||||
| 
 | ||||
| branches: | ||||
|   only: | ||||
|     - master | ||||
|     - /^v\d+\.\d+\.\d+$/ | ||||
| 
 | ||||
| sudo: false | ||||
| 
 | ||||
| jdk: | ||||
|   - oraclejdk8 | ||||
| 
 | ||||
| env: | ||||
|   global: | ||||
|     # Where to run androidTests | ||||
|     - EMULATOR_API=22 # 24 has some issues, probably some overlayed window | ||||
|     - EMULATOR_ABI=armeabi-v7a | ||||
|     - EMULATOR_TAG=default | ||||
|     - PATH=$ANDROID_HOME:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH | ||||
| 
 | ||||
| android: | ||||
|   components: | ||||
|     - tools | ||||
|     - platform-tools | ||||
|     - build-tools-28.0.2 | ||||
|     - android-28 | ||||
|     - doc-28 | ||||
| 
 | ||||
| install: | ||||
|   # Setup | ||||
|   - echo $ANDROID_HOME # We assume this is correctly set when setting path | ||||
|   - sdkmanager --list || true # Look at the packages | ||||
|   - echo yes | sdkmanager "tools" # Ensure tools is updated | ||||
|   - echo yes | sdkmanager "emulator" # Ensure emulator is present | ||||
| 
 | ||||
|   # Install emulator | ||||
|   - export EMULATOR="system-images;android-$EMULATOR_API;$EMULATOR_TAG;$EMULATOR_ABI" | ||||
|   - echo yes | sdkmanager "platforms;android-$EMULATOR_API" # Install sdk | ||||
|   - echo yes | sdkmanager "$EMULATOR" # Install system image | ||||
|   - sdkmanager --list || true # Check everything is updated | ||||
| 
 | ||||
|   # Create adn start emulator | ||||
|   - echo no | avdmanager create avd -n test -k "$EMULATOR" -f # Create emulator | ||||
|   - which emulator # ensure we are using the right emulator (home/emulator/) | ||||
|   - emulator -avd test -no-window -camera-back emulated -camera-front emulated -memory 2048 -writable-system & # Launch | ||||
|   - adb wait-for-device # Wait for adb process | ||||
|   - adb remount # Mount as writable | ||||
| 
 | ||||
| before_script: | ||||
|   # Wait for emulator | ||||
|   - android-wait-for-emulator # Wait for emulator ready to interact | ||||
|   - adb shell settings put global window_animation_scale 0 & # Disable animations | ||||
|   - adb shell settings put global transition_animation_scale 0 & # Disable animations | ||||
|   - adb shell settings put global animator_duration_scale 0 & # Disable animations | ||||
| 
 | ||||
|   # Unlock and configure logs. | ||||
|   # Would be great to use -v color to adb logcat but looks not supported on travis. | ||||
|   - sleep 20 # Sleep 20 seconds just in case | ||||
|   - adb shell input keyevent 82 & # Dispatch unlock event | ||||
|   - adb logcat --help # See if this version supports color | ||||
|   - adb logcat -c # Clear logcat | ||||
|   - adb logcat Test:V TestRunner:V CameraView:V CameraController:V Camera1:V WorkerHandler:V THREAD_STATE:S *:E & | ||||
|   # - export LOGCAT_PID=$! # Save PID of the logcat process. Should kill later with kill $LOGCAT_PID | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| script: | ||||
|   - ./gradlew clean testDebugUnitTest connectedCheck mergedCoverageReport | ||||
| 
 | ||||
| after_success: | ||||
|   - bash <(curl -s https://codecov.io/bash) -s "*/build/reports/mergedCoverageReport/" | ||||
| 
 | ||||
| cache: | ||||
|   directories: | ||||
|     - $HOME/.gradle | ||||
|     - $HOME/.m2/repository | ||||
| 
 | ||||
| deploy: | ||||
|   provider: script | ||||
|   script: ./gradlew bintrayUpload | ||||
|   skip_cleanup: true | ||||
|   on: | ||||
|     branch: master | ||||
|     tags: true | ||||
| @ -1,156 +0,0 @@ | ||||
| ## v1.6.1 | ||||
| 
 | ||||
| This is the last release before v2. | ||||
| 
 | ||||
| - Fixed: crash when using TextureView in API 28, thanks to [@Keyrillanskiy][Keyrillanskiy] ([#297][297]) | ||||
| - Fixed: restore Frame Processor callbacks after taking videos, thanks to [@stefanJi][stefanJi] ([#344][344]) | ||||
| - Enhancement: when horizontal, camera now uses the last available orientation, thanks to [@aartikov][aartikov] ([#290][290]) | ||||
| - Changed: we now swallow exceptions during autoFocus that were happening unpredictably on some devices, thanks to [@mahdi-ninja][mahdi-ninja] ([#332][332]) | ||||
| https://github.com/natario1/CameraView/compare/v1.6.0...v1.6.1 | ||||
| 
 | ||||
| ## v1.6.0 | ||||
| 
 | ||||
| - Lifecycle support. Use `setLifecycleOwner` instead of calling start, stop and destroy ([#265][265]) | ||||
| - Enhancement: provide synchronous version of CameraUtils.decodeBitmap thanks to [@athornz][athornz] ([#224][224]) | ||||
| - Enhancement: prevent possible context leak thanks to [@MatFl][MatFl] ([#245][245]) | ||||
| - Bug: fix crash when using default VideoCodec thanks to [@Namazed][Namazed] ([#264][264]) | ||||
| - Enhancement: CameraException.getReason() gives some insight about the error ([#265][265]) | ||||
| - Enhancement: Common crashes are now being posted to the error callback instead of crashing the app ([#265][265]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.5.1...v1.6.0 | ||||
| 
 | ||||
| ### v1.5.1 | ||||
| 
 | ||||
| - Bug: byte array length for Frames was incorrect thanks to [@ssakhavi][ssakhavi] ([#205][205]) | ||||
| - Bug: gestures were crashing in some conditions ([#222][222]) | ||||
| - Bug: import correctly the ExifInterface library ([#222][222]) | ||||
| - Updated dependencies thanks to [@caleb-allen][caleb-allen] ([#190][190]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.5.0...v1.5.1 | ||||
| 
 | ||||
| ## v1.5.0 | ||||
| 
 | ||||
| - New: set encoder for video recordings with `cameraVideoCodec` ([#174][174]) | ||||
| - New: set max duration for videos with `cameraVideoMaxDuration` ([#172][172]) | ||||
| - Enhancement: reduced lag with continuous gestures (ev, zoom) ([#170][170]) | ||||
| - Bug: tap to focus was crashing on some devices ([#167][167]) | ||||
| - Bug: capturePicture was breaking if followed by another event soon after ([#173][173]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.4.2...v1.5.0 | ||||
| 
 | ||||
| ### v1.4.2 | ||||
| 
 | ||||
| - Add prefix to XML resources so they don't collide, thanks to [@RocketRider][RocketRider] ([#162][162]) | ||||
| - Add `videoMaxSize` API and XML attribute, to set max size video in bytes, thanks to [@chaitanyaraghav][chaitanyaraghav] ([#104][104]) | ||||
| - Improved the preview size selection, thanks to [@YeungKC][YeungKC] ([#133][133]) | ||||
| - Improved the playSounds attribute, was playing incorrectly, thanks to [@xp-vit][xp-vit] ([#143][143]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.4.1...v1.4.2 | ||||
| 
 | ||||
| ### v1.4.1 | ||||
| 
 | ||||
| - Fixed a bug that would flip the front camera preview on some devices ([#112][112]) | ||||
| - Two new `CameraOptions` APIs: `o.getSupportedPictureSizes()` and `o.getSupportedPictureAspectRatios()` ([#101][101]) | ||||
| - Most controls (video quality, hdr, grid, session type, audio, white balance, flash, facing) now inherit | ||||
|   from a base `Control` class ([#105][105]). This let us add new APIs: | ||||
| 
 | ||||
|   - `CameraView.set(Control)`: sets the control to the given value, e.g. `set(Flash.AUTO)` | ||||
|   - `CameraOptions.supports(Control)`: returns true if the control is supported | ||||
|   - `CameraOptions.getSupportedControls(Class<? extends Control>)`: returns list of supported controls of a given kind | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.4.0...v1.4.1 | ||||
| 
 | ||||
| ## v1.4.0 | ||||
| 
 | ||||
| - CameraView is now completely thread-safe. All actions are asynchronous. ([#97][97]) | ||||
|   This has some breaking drawbacks. Specifically, the `get` methods (e.g., `getWhiteBalance`) might | ||||
|   not return the correct value while it is being changed. So don't trust them right after you have changed the value. | ||||
|   Instead, always check the `CameraOptions` to see if the value you want is supported. | ||||
| - Added error handling ([#97][97]) in `CameraListener.onCameraError(CameraException)`. | ||||
|   At the moment, all exceptions there are unrecoverable. When the method is called, the camera is showing | ||||
|   a black preview. This is a good moment to show an error dialog to the user. | ||||
|   You can also try to `start()` again but that is not guaranteed to work. | ||||
| - Long requested ability to set the picture output size ([#99][99]). Can be done through | ||||
|   `CameraView.setPictureSize()` or through new XML attributes starting with `cameraPictureSize`. | ||||
|   Please refer to docs about it. | ||||
| - Deprecated `toggleFacing`. It was unreliable and will be removed. | ||||
| - Deprecated `getCaptureSize`. Use `getPictureSize` instead. | ||||
| - Fixed bugs. | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.3.2...v1.4.0 | ||||
| 
 | ||||
| ### v1.3.2 | ||||
| 
 | ||||
| - Fixed a memory leak thanks to [@andrewmunn][andrewmunn] ([#92][92]) | ||||
| - Reduced memory usage when using cropOutput thanks to [@RobertoMorelos][RobertoMorelos] ([#93][93]) | ||||
| - Improved efficiency for Frame processors, recycle buffers and Frames ([#94][94]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.3.1...v1.3.2 | ||||
| 
 | ||||
| ### v1.3.1 | ||||
| 
 | ||||
| - Fixed a bug that would make setFacing and other APIs freeze the camera ([#86][86]) | ||||
| - Fixed ConcurrentModificationExceptions during CameraListener callbacks ([#88][88]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.3.0...v1.3.1 | ||||
| 
 | ||||
| ## v1.3.0 | ||||
| 
 | ||||
| - Ability to inject frame processors to do your own visual tasks (barcodes, facial recognition etc.) ([#82][82]) | ||||
| - Ability to inject external loggers (e.g. Crashlytics) to listen for internal logging events ([#80][80]) | ||||
| - Improved CameraUtils.decodeBitmap, you can now pass maxWidth and maxHeight to avoid OOM ([#83][83]) | ||||
| - Updated dependencies thanks to [@v-gar][v-gar] ([#73][73]) | ||||
| 
 | ||||
| https://github.com/natario1/CameraView/compare/v1.2.3...v1.3.0 | ||||
| 
 | ||||
| [aartikov]: https://github.com/aartikov | ||||
| [athornz]: https://github.com/athornz | ||||
| [v-gar]: https://github.com/v-gar | ||||
| [andrewmunn]: https://github.com/andrewmunn | ||||
| [chaitanyaraghav]: https://github.com/chaitanyaraghav | ||||
| [YeungKC]: https://github.com/YeungKC | ||||
| [RobertoMorelos]: https://github.com/RobertoMorelos | ||||
| [RocketRider]: https://github.com/RocketRider | ||||
| [xp-vit]: https://github.com/xp-vit | ||||
| [caleb-allen]: https://github.com/caleb-allen | ||||
| [ssakhavi]: https://github.com/ssakhavi | ||||
| [MatFl]: https://github.com/MatFl | ||||
| [Namazed]: https://github.com/Namazed | ||||
| [Keyrillanskiy]: https://github.com/Keyrillanskiy | ||||
| [mahdi-ninja]: https://github.com/mahdi-ninja | ||||
| [stefanJi]: https://github.com/stefanJi | ||||
| 
 | ||||
| [73]: https://github.com/natario1/CameraView/pull/73 | ||||
| [80]: https://github.com/natario1/CameraView/pull/80 | ||||
| [82]: https://github.com/natario1/CameraView/pull/82 | ||||
| [83]: https://github.com/natario1/CameraView/pull/83 | ||||
| [86]: https://github.com/natario1/CameraView/pull/86 | ||||
| [88]: https://github.com/natario1/CameraView/pull/88 | ||||
| [92]: https://github.com/natario1/CameraView/pull/92 | ||||
| [93]: https://github.com/natario1/CameraView/pull/93 | ||||
| [94]: https://github.com/natario1/CameraView/pull/94 | ||||
| [97]: https://github.com/natario1/CameraView/pull/97 | ||||
| [99]: https://github.com/natario1/CameraView/pull/99 | ||||
| [101]: https://github.com/natario1/CameraView/pull/101 | ||||
| [104]: https://github.com/natario1/CameraView/pull/104 | ||||
| [105]: https://github.com/natario1/CameraView/pull/105 | ||||
| [112]: https://github.com/natario1/CameraView/pull/112 | ||||
| [133]: https://github.com/natario1/CameraView/pull/133 | ||||
| [143]: https://github.com/natario1/CameraView/pull/143 | ||||
| [162]: https://github.com/natario1/CameraView/pull/162 | ||||
| [167]: https://github.com/natario1/CameraView/pull/167 | ||||
| [170]: https://github.com/natario1/CameraView/pull/170 | ||||
| [172]: https://github.com/natario1/CameraView/pull/172 | ||||
| [173]: https://github.com/natario1/CameraView/pull/173 | ||||
| [174]: https://github.com/natario1/CameraView/pull/174 | ||||
| [190]: https://github.com/natario1/CameraView/pull/190 | ||||
| [205]: https://github.com/natario1/CameraView/pull/205 | ||||
| [222]: https://github.com/natario1/CameraView/pull/222 | ||||
| [224]: https://github.com/natario1/CameraView/pull/224 | ||||
| [245]: https://github.com/natario1/CameraView/pull/245 | ||||
| [264]: https://github.com/natario1/CameraView/pull/264 | ||||
| [265]: https://github.com/natario1/CameraView/pull/265 | ||||
| [290]: https://github.com/natario1/CameraView/pull/290 | ||||
| [297]: https://github.com/natario1/CameraView/pull/297 | ||||
| [332]: https://github.com/natario1/CameraView/pull/332 | ||||
| [334]: https://github.com/natario1/CameraView/pull/334 | ||||
| @ -0,0 +1,128 @@ | ||||
| ⠀ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ¿Postprocesar videos o quieres reducir el tamaño del video antes de subirlo? Eche un vistazo a nuestro transcodificador . | ||||
| 
 | ||||
| ¿Le gusta el proyecto, sacar provecho de él o simplemente quiere agradecerle? ¡Considere patrocinarme o donar ! | ||||
| 
 | ||||
| ¿Necesita soporte, consultoría o tiene alguna otra pregunta relacionada con el negocio? No dude en ponerse en contacto . | ||||
| 
 | ||||
| Vista de cámara | ||||
| CameraView es una biblioteca de alto nivel bien documentada que facilita la captura de imágenes y videos, aborda la mayoría de los problemas y necesidades comunes, y aún lo deja con flexibilidad donde sea necesario. | ||||
| 
 | ||||
| api ' com.otaliastudios: cameraview: 2.7.0 ' | ||||
| Rápido y confiable | ||||
| Soporte de gestos [docs] | ||||
| Filtros en tiempo real [documentos] | ||||
| Motor impulsado por Camera1 o Camera2 [docs] | ||||
| Soporte de procesamiento de fotogramas [docs] | ||||
| Marcas de agua y superposiciones animadas [docs] | ||||
| Vista previa con tecnología OpenGL [docs] | ||||
| Toma contenido de alta calidad con takePicturey takeVideo [docs] | ||||
| Toma instantáneas súper rápidas con takePictureSnapshoty takeVideoSnapshot [docs] | ||||
| CameraViewTamaño inteligente: cree un tamaño de cualquier tamaño [docs] | ||||
| Controla HDR, flash, zoom, balance de blancos, exposición, ubicación, dibujo de cuadrícula y más [docs] | ||||
| Soporte de imágenes RAW [docs] | ||||
| Ligero | ||||
| Funciona hasta el nivel de API 15 | ||||
| Bien probado | ||||
| ⠀ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ⠀ | ||||
| 
 | ||||
| Apoyo | ||||
| Si te gusta el proyecto, obtienes beneficios de él o simplemente quieres agradecer, ¡considera patrocinarme a través del programa de patrocinadores de GitHub! Puede tener el logotipo de su empresa aquí, obtener horas de soporte privado o simplemente ayudarme a impulsar esto. Si lo prefiere, también puede donar a nuestra página OpenCollective. | ||||
| 
 | ||||
| CameraView cuenta con el respaldo de ShareChat , una aplicación de redes sociales con más de 100 millones de descargas. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| No dude en contactarme para soporte, consultoría o cualquier otra pregunta relacionada con el negocio. | ||||
| 
 | ||||
| Gracias a todos los patrocinadores de nuestro proyecto ... [conviértete en patrocinador] | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ... ¡ya todos nuestros patrocinadores de proyectos! [conviértete en patrocinador] | ||||
| 
 | ||||
|           | ||||
| 
 | ||||
| Preparar | ||||
| Lea el sitio web oficial para obtener instrucciones de configuración y documentación. También puede estar interesado en nuestro registro de cambios o en la guía de migración v1 . Usar CameraView es extremadamente simple: | ||||
| 
 | ||||
| < com .otaliastudios.cameraview.CameraView | ||||
|      xmlns : app = " http://schemas.android.com/apk/res-auto " | ||||
|      android : layout_width = " wrap_content " | ||||
|      android : layout_height = " wrap_content " | ||||
|      aplicación : cameraPictureSizeMinWidth = " @integer / picture_min_width " | ||||
|      aplicación : cameraPictureSizeMinHeight = " @ integer / picture_min_height " | ||||
|      aplicación : cameraPictureSizeMaxWidth= " @ Entero / picture_max_width " | ||||
|      aplicación : cameraPictureSizeMaxHeight = " @ entero / picture_max_height " | ||||
|      aplicación : cameraPictureSizeMinArea = " @ entero / picture_min_area " | ||||
|      aplicación : cameraPictureSizeMaxArea = " @ entero / picture_max_area " | ||||
|      aplicación : cameraPictureSizeSmallest = " falsa | verdadera " | ||||
|      aplicación : cameraPictureSizeBiggest = " falsa | verdadera " | ||||
|      aplicación :cameraPictureSizeAspectRatio = " @ string / video_ratio " | ||||
|      aplicación : cameraVideoSizeMinWidth = " @ número entero / video_min_width " | ||||
|      aplicación : cameraVideoSizeMinHeight = " @ número entero / video_min_height " | ||||
|      aplicación : cameraVideoSizeMaxWidth = " @ número entero / video_max_width " | ||||
|      aplicación : cameraVideoSizeMaxHeight = " @ número entero / video_max_height " | ||||
|      aplicación : cameraVideoSizeMinArea = " @ integer / video_min_area " | ||||
|     aplicación: CameraVideoSizeMaxArea = " @ entero / video_max_area " | ||||
|      aplicación : cameraVideoSizeSmallest = " falsa | verdadera " | ||||
|      aplicación : cameraVideoSizeBiggest = " falsa | verdadera " | ||||
|      aplicación : cameraVideoSizeAspectRatio = " @ string / video_ratio " | ||||
|      aplicación : cameraSnapshotMaxWidth = " @ entero / snapshot_max_width " | ||||
|      aplicación : cameraSnapshotMaxHeight = " @ número entero / snapshot_max_height " | ||||
|      aplicación :cameraFrameProcessingMaxWidth = " @ número entero / processing_max_width " | ||||
|      aplicación : cameraFrameProcessingMaxHeight = " @ número entero / processing_max_height " | ||||
|      aplicación : cameraFrameProcessingFormat = " @ número entero / processing_format " | ||||
|      aplicación : cameraFrameProcessingPoolSize = " @ número entero / processing_pool_size " | ||||
|      app : cameraFrameProcessingExecutors = " @ número entero / processing_executors " | ||||
|      aplicación : cameraVideoBitRate = "@ integer / video_bit_rate " | ||||
|      app : cameraAudioBitRate = " @ integer / audio_bit_rate " | ||||
|      app : cameraGestureTap = " none | autoFocus | takePicture " | ||||
|      aplicación : cameraGestureLongTap = " none | autoFocus | takePicture " | ||||
|      aplicación : cameraGesturePinch = " ninguno | zoom | exposiciónCorrección | filter filterControl2 " | ||||
|      aplicación : cameraGestureScrollHorizontal = " none | zoom | exposiciónCorrection | filterControl1 | filterControl2 " | ||||
|      aplicación: CameraGestureScrollVertical = " none | zoom | exposureCorrection | filterControl1 | filterControl2 " | ||||
|      aplicación : cameraEngine = " camera1 | camera2 " | ||||
|      aplicación : cameraPreview = " glSurface | superficie | textura " | ||||
|      aplicación : cameraPreviewFrameRate = " @ entero / preview_frame_rate " | ||||
|      aplicación : cameraPreviewFrameRateExact = " falsa | verdadero " | ||||
|      app : cameraFacing = " back | front " | ||||
|     aplicación :cameraHdr = " on | off " | ||||
|      aplicación : cameraFlash = " on | auto | antorcha | off " | ||||
|      aplicación : cameraWhiteBalance = " auto | nublado | luz del día | fluorescente | incandescente " | ||||
|      aplicación : cameraMode = " imagen | video " | ||||
|      aplicación : cameraAudio = " on | off | mono | stereo " | ||||
|      aplicación : cameraGrid = " draw3x3 | draw4x4 | drawPhi | off " | ||||
|      aplicación : cameraGridColor = "@ color / grid_color " | ||||
|      app : cameraPlaySounds = " true | false " | ||||
|      app : cameraVideoMaxSize = " @ entero / video_max_size " | ||||
|      aplicación : cameraVideoMaxDuration = " @ entero / video_max_duration " | ||||
|      aplicación : cameraVideoCodec = " deviceDefault | H264 | h263 " | ||||
|      aplicación : cameraAutoFocusResetDelay = " @ integer / autofocus_delay " | ||||
|      aplicación : cameraAutoFocusMarker = "@ string / cameraview_default_autofocus_marker " | ||||
|      app : cameraUseDeviceOrientation = " true | false " | ||||
|      app : cameraFilter = " @ string / real_time_filter " | ||||
|      app : cameraPictureMetering = " true | false " | ||||
|      app : cameraPictureSnapshotMetering = " false | true " | ||||
|      app : cameraPictureFormat = " jpeg | " | ||||
|      app : cameraRequestPermissions = " true | false" | ||||
|      app : cameraExperimental = " false | true " > | ||||
|      | ||||
|     <! - Marca de agua! ->  | ||||
|     < ImageView  | ||||
|         android : layout_width = " wrap_content " | ||||
|          android : layout_height = " wrap_content " | ||||
|          android : layout_gravity = " bottom | end " | ||||
|          android : src = " @ drawable / watermark " | ||||
|          app : layout_drawOnPreview = " true | false " | ||||
|          aplicación : layout_drawOnPictureSnapshot = " verdadero | falso" | ||||
|          aplicación : layout_drawOnVideoSnapshot = " true | false " /> | ||||
|          | ||||
| </ com .otaliastudios.cameraview.CameraVi | ||||
| Before Width: | Height: | Size: 122 KiB | 
| Before Width: | Height: | Size: 1.5 MiB | 
| Before Width: | Height: | Size: 106 KiB | 
| Before Width: | Height: | Size: 113 KiB | 
| Before Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 46 KiB | 
| @ -1,35 +0,0 @@ | ||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||
| 
 | ||||
| buildscript { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|         google() | ||||
|     } | ||||
| 
 | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:3.2.0-rc03' | ||||
|         // https://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en | ||||
|         // https://www.theguardian.com/technology/developer-blog/2016/dec/06/how-to-publish-an-android-library-a-mysterious-conversation | ||||
|         classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' | ||||
|         classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| allprojects { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|         google() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ext { | ||||
|     compileSdkVersion = 28 | ||||
|     supportLibVersion = '28.0.0-rc02' | ||||
|     lifecycleVersion = '1.1.1' | ||||
|     minSdkVersion = 15 | ||||
|     targetSdkVersion = 28 | ||||
| } | ||||
| 
 | ||||
| task clean(type: Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| 
 | ||||
| buildscript { | ||||
| 
 | ||||
|     extra["minSdkVersion"] = 15 | ||||
|     extra["compileSdkVersion"] = 30 | ||||
|     extra["targetSdkVersion"] = 30 | ||||
| 
 | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         jcenter() | ||||
|     } | ||||
| 
 | ||||
|     dependencies { | ||||
|         classpath("com.android.tools.build:gradle:4.1.2") | ||||
|         classpath("io.deepmedia.tools:publisher:0.4.1") | ||||
|         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21") | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| allprojects { | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         jcenter() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| tasks.register("clean", Delete::class) { | ||||
|     delete(buildDir) | ||||
| } | ||||
| @ -1,217 +0,0 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply plugin: 'com.github.dcendents.android-maven' | ||||
| apply plugin: 'com.jfrog.bintray' | ||||
| 
 | ||||
| // Required by bintray | ||||
| version = '1.6.1' | ||||
| group = 'com.otaliastudios' | ||||
| 
 | ||||
| //region android dependencies | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion rootProject.ext.compileSdkVersion | ||||
|     // buildToolsVersion rootProject.ext.buildToolsVersion | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion rootProject.ext.minSdkVersion | ||||
|         targetSdkVersion rootProject.ext.targetSdkVersion | ||||
|         versionCode 1 | ||||
|         versionName project.version | ||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
|     } | ||||
| 
 | ||||
|     buildTypes { | ||||
|         debug { | ||||
|             testCoverageEnabled true | ||||
|         } | ||||
| 
 | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     sourceSets { | ||||
|         main.java.srcDirs += 'src/main/options' | ||||
|         main.java.srcDirs += 'src/main/views' | ||||
|         main.java.srcDirs += 'src/main/utils' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     testImplementation 'org.mockito:mockito-core:1.10.19' | ||||
| 
 | ||||
|     androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||||
|     androidTestImplementation 'com.android.support.test:rules:1.0.2' | ||||
|     androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' | ||||
|     androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' | ||||
|     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||||
| 
 | ||||
|     api "com.android.support:exifinterface:$supportLibVersion" | ||||
|     api "android.arch.lifecycle:common:$lifecycleVersion" | ||||
|     implementation "com.android.support:support-annotations:$supportLibVersion" | ||||
| } | ||||
| 
 | ||||
| //endregion | ||||
| 
 | ||||
| //region bintray | ||||
| 
 | ||||
| install { | ||||
|     repositories.mavenInstaller { | ||||
|         pom.project { | ||||
|             name 'CameraView' | ||||
|             description 'A well documented, high-level Android interface that makes capturing pictures ' + | ||||
|                     'and videos easy, addressing most of the common issues and needs.' | ||||
|             url 'https://github.com/natario1/CameraView' | ||||
| 
 | ||||
|             packaging 'aar' | ||||
|             groupId project.group | ||||
|             artifactId 'cameraview' | ||||
|             version project.version | ||||
| 
 | ||||
|             licenses { | ||||
|                 license { | ||||
|                     name 'The Apache Software License, Version 2.0' | ||||
|                     url 'http://www.apache.org/licenses/LICENSE-2.0.txt' | ||||
|                 } | ||||
|             } | ||||
|             scm { | ||||
|                 connection 'https://github.com/natario1/CameraView.git' | ||||
|                 developerConnection 'https://github.com/natario1/CameraView.git' | ||||
|                 url 'https://github.com/natario1/CameraView' | ||||
| 
 | ||||
|             } | ||||
|             developers { | ||||
|                 developer { | ||||
|                     id = 'natario' | ||||
|                     name 'Natario' | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| def bintrayUser | ||||
| def bintrayKey | ||||
| def travis = System.getenv("TRAVIS") | ||||
| if (travis) { | ||||
|     bintrayUser = System.getenv("BINTRAY_USER") | ||||
|     bintrayKey = System.getenv("BINTRAY_KEY") | ||||
| } else { | ||||
|     Properties props = new Properties() | ||||
|     props.load(project.rootProject.file('local.properties').newDataInputStream()) | ||||
|     bintrayUser = props.getProperty('bintray.user') | ||||
|     bintrayKey = props.get('bintray.key') | ||||
| } | ||||
| 
 | ||||
| bintray { | ||||
|     // https://github.com/bintray/gradle-bintray-plugin | ||||
|     user = bintrayUser | ||||
|     key = bintrayKey | ||||
|     configurations = ['archives'] | ||||
|     pkg { | ||||
|         repo = 'android' | ||||
|         name = 'CameraView' | ||||
|         licenses = ['Apache-2.0'] | ||||
|         vcsUrl = 'https://github.com/natario1/CameraView.git' | ||||
|         publish = true | ||||
|         override = true | ||||
|         version { | ||||
|             name = project.version | ||||
|             desc = 'CameraView v. '+project.version | ||||
|             released = new Date() | ||||
|             vcsTag = 'v'+project.version | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| //endregion | ||||
| 
 | ||||
| //region javadoc and sources | ||||
| 
 | ||||
| // From official sample https://github.com/bintray/bintray-examples/blob/master/gradle-aar-example/build.gradle | ||||
| task sourcesJar(type: Jar) { | ||||
|     classifier = 'sources' | ||||
|     from android.sourceSets.main.java.sourceFiles | ||||
| } | ||||
| 
 | ||||
| task javadoc(type: Javadoc) { | ||||
|     source = android.sourceSets.main.java.srcDirs | ||||
|     classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) | ||||
|     classpath += project.files("${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar") | ||||
|     project.android.libraryVariants.all { variant -> | ||||
|         if (variant.name == 'release') { | ||||
|             classpath += files(variant.javaCompile.classpath) | ||||
|         } | ||||
|     } | ||||
|     exclude '**/BuildConfig.java' | ||||
|     exclude '**/R.java' | ||||
|     exclude '**/internal/**' | ||||
| } | ||||
| 
 | ||||
| task javadocJar(type: Jar, dependsOn: javadoc) { | ||||
|     classifier = 'javadoc' | ||||
|     from javadoc.destinationDir | ||||
| } | ||||
| 
 | ||||
| artifacts { | ||||
|     archives javadocJar | ||||
|     archives sourcesJar | ||||
| } | ||||
| 
 | ||||
| //endregion | ||||
| 
 | ||||
| //region code coverage | ||||
| 
 | ||||
| // 1. running androidTests with connectedCheck will generate an .ec file | ||||
| // in build/outputs/code-coverage/connected, plus the XML result in | ||||
| // in build/reports/coverage/debug/report.xml . | ||||
| 
 | ||||
| // 2. running unit tests with testDebugUnitTest will just generate the .exec file. | ||||
| // The JacocoReport task from the jacoco plugin can create the XML report out of it. | ||||
| 
 | ||||
| // to have a unified report, we just pass both the .exec and the .ec file | ||||
| // to the jacoco task, so we get a unified XML report with total coverage. | ||||
| // Reference: https://medium.com/@rafael_toledo/setting-up-an-unified-coverage-report-in-android-with-jacoco-robolectric-and-espresso-ffe239aaf3fa | ||||
| 
 | ||||
| apply plugin: 'jacoco' | ||||
| 
 | ||||
| def reportsDirectory = "$buildDir/reports/" | ||||
| jacoco { | ||||
|     toolVersion = "0.8.1" | ||||
|     reportsDir = file(reportsDirectory) | ||||
| } | ||||
| 
 | ||||
| task mergedCoverageReport(type: JacocoReport) { | ||||
|     dependsOn "testDebugUnitTest" | ||||
|     dependsOn "connectedCheck" | ||||
| 
 | ||||
|     def testData = "jacoco/testDebugUnitTest.exec" | ||||
|     def androidTestData = "outputs/code-coverage/connected/*coverage.ec" | ||||
|     executionData = fileTree(dir: "$buildDir", includes: [testData, androidTestData]) | ||||
| 
 | ||||
|     // Sources. | ||||
|     sourceDirectories = android.sourceSets.main.java.sourceFiles | ||||
|     // Add BuildConfig and R. | ||||
|     additionalSourceDirs = files([ | ||||
|             "$buildDir/generated/source/buildConfig/debug", | ||||
|             "$buildDir/generated/source/r/debug" | ||||
|     ]) | ||||
| 
 | ||||
|     // Classes. | ||||
|     def debugDir = "$buildDir/intermediates/classes/debug" | ||||
|     def filter = ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', | ||||
|                   '**/BuildConfig.*', '**/Manifest*.*'] | ||||
|     classDirectories = fileTree(dir: debugDir, excludes: filter); | ||||
| 
 | ||||
|     reports.xml.enabled = true | ||||
|     reports.html.enabled = true | ||||
|     reports.xml.destination = "$reportsDirectory/mergedCoverageReport/report.xml" | ||||
| } | ||||
| 
 | ||||
| //endregion | ||||
| 
 | ||||
| // export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home | ||||
| // To deploy ./gradlew bintrayUpload | ||||
| 
 | ||||
| @ -0,0 +1,125 @@ | ||||
| import io.deepmedia.tools.publisher.common.License | ||||
| import io.deepmedia.tools.publisher.common.Release | ||||
| 
 | ||||
| plugins { | ||||
|     id("com.android.library") | ||||
|     id("kotlin-android") | ||||
|     id("io.deepmedia.tools.publisher") | ||||
|     id("jacoco") | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     setCompileSdkVersion(property("compileSdkVersion") as Int) | ||||
|     defaultConfig { | ||||
|         setMinSdkVersion(property("minSdkVersion") as Int) | ||||
|         setTargetSdkVersion(property("targetSdkVersion") as Int) | ||||
|         versionCode = 1 | ||||
|         versionName = "2.7.0" | ||||
|         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||||
|         testInstrumentationRunnerArgument("filter", "" + | ||||
|                 "com.otaliastudios.cameraview.tools.SdkExcludeFilter," + | ||||
|                 "com.otaliastudios.cameraview.tools.SdkIncludeFilter") | ||||
|     } | ||||
|     buildTypes["debug"].isTestCoverageEnabled = true | ||||
|     buildTypes["release"].isMinifyEnabled = false | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     testImplementation("junit:junit:4.13") | ||||
|     testImplementation("org.mockito:mockito-inline:2.28.2") | ||||
| 
 | ||||
|     androidTestImplementation("androidx.test:runner:1.3.0") | ||||
|     androidTestImplementation("androidx.test:rules:1.3.0") | ||||
|     androidTestImplementation("androidx.test.ext:junit:1.1.2") | ||||
|     androidTestImplementation("org.mockito:mockito-android:2.28.2") | ||||
|     androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0") | ||||
| 
 | ||||
|     api("androidx.exifinterface:exifinterface:1.3.2") | ||||
|     api("androidx.lifecycle:lifecycle-common:2.2.0") | ||||
|     api("com.google.android.gms:play-services-tasks:17.2.0") | ||||
|     implementation("androidx.annotation:annotation:1.1.0") | ||||
|     implementation("com.otaliastudios.opengl:egloo:0.5.3") | ||||
| } | ||||
| 
 | ||||
| // Publishing | ||||
| 
 | ||||
| publisher { | ||||
|     project.description = "A well documented, high-level Android interface that makes capturing " + | ||||
|             "pictures and videos easy, addressing all of the common issues and needs. " + | ||||
|             "Real-time filters, gestures, watermarks, frame processing, RAW, output of any size." | ||||
|     project.artifact = "cameraview" | ||||
|     project.group = "com.otaliastudios" | ||||
|     project.url = "https://github.com/natario1/CameraView" | ||||
|     project.addLicense(License.APACHE_2_0) | ||||
|     bintray { | ||||
|         release.sources = Release.SOURCES_AUTO | ||||
|         release.docs = Release.DOCS_AUTO | ||||
|         auth.user = "BINTRAY_USER" | ||||
|         auth.key = "BINTRAY_KEY" | ||||
|         auth.repo = "BINTRAY_REPO" | ||||
|     } | ||||
|     directory { | ||||
|         directory = file(repositories.mavenLocal().url).absolutePath | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Code Coverage | ||||
| val buildDir = project.buildDir.absolutePath | ||||
| val coverageInputDir = "$buildDir/coverage_input" // changing? change github workflow | ||||
| val coverageOutputDir = "$buildDir/coverage_output" // changing? change github workflow | ||||
| 
 | ||||
| // Run unit tests, with coverage enabled in the android { } configuration. | ||||
| // Output will be an .exec file in build/jacoco. | ||||
| tasks.register("runUnitTests") { // changing name? change github workflow | ||||
|     dependsOn("testDebugUnitTest") | ||||
|     doLast { | ||||
|         copy { | ||||
|             from("$buildDir/jacoco/testDebugUnitTest.exec") | ||||
|             into("$coverageInputDir/unit_tests") // changing? change github workflow | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Run android tests with coverage. | ||||
| tasks.register("runAndroidTests") { // changing name? change github workflow | ||||
|     dependsOn("connectedDebugAndroidTest") | ||||
|     doLast { | ||||
|         copy { | ||||
|             from("$buildDir/outputs/code_coverage/debugAndroidTest/connected") | ||||
|             include("*coverage.ec") | ||||
|             into("$coverageInputDir/android_tests") // changing? change github workflow | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Merge the two with a jacoco task. | ||||
| jacoco { toolVersion = "0.8.5" } | ||||
| tasks.register("computeCoverage", JacocoReport::class) { | ||||
|     dependsOn("compileDebugSources") // Compile sources, needed below | ||||
|     executionData.from(fileTree(coverageInputDir)) | ||||
|     sourceDirectories.from(android.sourceSets["main"].java.srcDirs) | ||||
|     additionalSourceDirs.from("$buildDir/generated/source/buildConfig/debug") | ||||
|     additionalSourceDirs.from("$buildDir/generated/source/r/debug") | ||||
|     classDirectories.from(fileTree("$buildDir/intermediates/javac/debug") { | ||||
|         // Not everything here is relevant for CameraView, but let's keep it generic | ||||
|         exclude( | ||||
|                 "**/R.class", | ||||
|                 "**/R$*.class", | ||||
|                 "**/BuildConfig.*", | ||||
|                 "**/Manifest*.*", | ||||
|                 "android/**", | ||||
|                 "androidx/**", | ||||
|                 "com/google/**", | ||||
|                 "**/*\$ViewInjector*.*", | ||||
|                 "**/Dagger*Component.class", | ||||
|                 "**/Dagger*Component\$Builder.class", | ||||
|                 "**/*Module_*Factory.class", | ||||
|                 // We don"t test OpenGL filters. | ||||
|                 "**/com/otaliastudios/cameraview/filters/**.*" | ||||
|         ) | ||||
|     }) | ||||
|     reports.html.isEnabled = true | ||||
|     reports.xml.isEnabled = true | ||||
|     reports.html.destination = file("$coverageOutputDir/html") | ||||
|     reports.xml.destination = file("$coverageOutputDir/xml/report.xml") | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.opengl.EGL14; | ||||
| 
 | ||||
| import com.otaliastudios.opengl.core.EglCore; | ||||
| import com.otaliastudios.opengl.surface.EglOffscreenSurface; | ||||
| import com.otaliastudios.opengl.surface.EglSurface; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| 
 | ||||
| 
 | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public abstract class BaseEglTest extends BaseTest { | ||||
| 
 | ||||
|     protected final static int WIDTH = 100; | ||||
|     protected final static int HEIGHT = 100; | ||||
| 
 | ||||
|     protected EglCore eglCore; | ||||
|     protected EglSurface eglSurface; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         eglCore = new EglCore(EGL14.EGL_NO_CONTEXT, EglCore.FLAG_RECORDABLE); | ||||
|         eglSurface = new EglOffscreenSurface(eglCore, WIDTH, HEIGHT); | ||||
|         eglSurface.makeCurrent(); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         eglSurface.release(); | ||||
|         eglSurface = null; | ||||
|         eglCore.release(); | ||||
|         eglCore = null; | ||||
|     } | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -1,76 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.graphics.Color; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.YuvImage; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.invocation.InvocationOnMock; | ||||
| import org.mockito.stubbing.Answer; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.OutputStream; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Matchers.anyInt; | ||||
| import static org.mockito.Mockito.anyString; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class CropHelperTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCropFromYuv() { | ||||
|         testCropFromYuv(1600, 1600, AspectRatio.of(16, 9)); | ||||
|         testCropFromYuv(1600, 1600, AspectRatio.of(9, 16)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCropFromJpeg() { | ||||
|         testCropFromJpeg(1600, 1600, AspectRatio.of(16, 9)); | ||||
|         testCropFromJpeg(1600, 1600, AspectRatio.of(9, 16)); | ||||
|     } | ||||
| 
 | ||||
|     private void testCropFromYuv(final int w, final int h, final AspectRatio target) { | ||||
|         final boolean wider = target.toFloat() > ((float) w / (float) h); | ||||
|         byte[] b = CropHelper.cropToJpeg(mockYuv(w, h), target, 100); | ||||
|         Bitmap result = BitmapFactory.decodeByteArray(b, 0, b.length); | ||||
| 
 | ||||
|         // Assert.
 | ||||
|         AspectRatio ratio = AspectRatio.of(result.getWidth(), result.getHeight()); | ||||
|         assertEquals(target, ratio); | ||||
|         if (wider) { // width must match.
 | ||||
|             assertEquals(result.getWidth(), w); | ||||
|         } else { | ||||
|             assertEquals(result.getHeight(), h); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void testCropFromJpeg(int w, int h, AspectRatio target) { | ||||
|         final boolean wider = target.toFloat() > ((float) w / (float) h); | ||||
|         byte[] b = CropHelper.cropToJpeg(mockJpeg(w, h), target, 100); | ||||
|         Bitmap result = BitmapFactory.decodeByteArray(b, 0, b.length); | ||||
| 
 | ||||
|         // Assert.
 | ||||
|         AspectRatio ratio = AspectRatio.of(result.getWidth(), result.getHeight()); | ||||
|         assertEquals(target, ratio); | ||||
|         if (wider) { // width must match.
 | ||||
|             assertEquals(result.getWidth(), w); | ||||
|         } else { | ||||
|             assertEquals(result.getHeight(), h); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,32 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.hardware.Camera; | ||||
| import android.hardware.camera2.CameraCharacteristics; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| import android.util.SizeF; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import static org.mockito.Mockito.*; | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class ExtraProperties1Test extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testConstructor1() { | ||||
|         Camera.Parameters params = mock(Camera.Parameters.class); | ||||
|         when(params.getVerticalViewAngle()).thenReturn(10f); | ||||
|         when(params.getHorizontalViewAngle()).thenReturn(5f); | ||||
|         ExtraProperties e = new ExtraProperties(params); | ||||
|         assertEquals(e.getVerticalViewingAngle(), 10f, 0f); | ||||
|         assertEquals(e.getHorizontalViewingAngle(), 5f, 0f); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,65 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.support.test.espresso.Espresso; | ||||
| import android.support.test.espresso.Root; | ||||
| import android.support.test.espresso.ViewAssertion; | ||||
| import android.support.test.espresso.ViewInteraction; | ||||
| import android.support.test.espresso.assertion.ViewAssertions; | ||||
| import android.support.test.espresso.matcher.RootMatchers; | ||||
| import android.support.test.espresso.matcher.ViewMatchers; | ||||
| import android.support.test.rule.ActivityTestRule; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import org.hamcrest.BaseMatcher; | ||||
| import org.hamcrest.Description; | ||||
| import org.hamcrest.Matchers; | ||||
| import org.junit.Before; | ||||
| import org.junit.Rule; | ||||
| 
 | ||||
| import static android.support.test.espresso.Espresso.onView; | ||||
| import static org.hamcrest.Matchers.any; | ||||
| 
 | ||||
| @TargetApi(17) | ||||
| public abstract class GestureLayoutTest<T extends GestureLayout> extends BaseTest { | ||||
| 
 | ||||
|     protected abstract T create(Context context); | ||||
| 
 | ||||
|     @Rule | ||||
|     public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class); | ||||
| 
 | ||||
|     protected T layout; | ||||
|     protected Task<Gesture> touch; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         ui(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 TestActivity a = rule.getActivity(); | ||||
|                 layout = create(a); | ||||
|                 layout.enable(true); | ||||
|                 a.inflate(layout); | ||||
| 
 | ||||
|                 touch = new Task<>(); | ||||
|                 layout.setOnTouchListener(new View.OnTouchListener() { | ||||
|                     @Override | ||||
|                     public boolean onTouch(View view, MotionEvent motionEvent) { | ||||
|                         boolean found = layout.onTouchEvent(motionEvent); | ||||
|                         if (found) touch.end(layout.getGestureType()); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     protected final ViewInteraction onLayout() { | ||||
|         return onView(Matchers.<View>is(layout)) | ||||
|                 .inRoot(RootMatchers.withDecorView( | ||||
|                         Matchers.is(rule.getActivity().getWindow().getDecorView()))); | ||||
|     } | ||||
| } | ||||
| @ -1,644 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.PointF; | ||||
| import android.hardware.Camera; | ||||
| import android.os.Build; | ||||
| import android.os.Handler; | ||||
| import android.support.test.filters.MediumTest; | ||||
| import android.support.test.rule.ActivityTestRule; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.BeforeClass; | ||||
| import org.junit.Ignore; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * These tests work great on real devices, and are the only way to test actual CameraController | ||||
|  * implementation - we really need to open the camera device. | ||||
|  * Unfortunately they fail unreliably on emulated devices, due to some bug with the | ||||
|  * emulated camera controller. Waiting for it to be fixed. | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @MediumTest | ||||
| @Ignore | ||||
| public class IntegrationTest extends BaseTest { | ||||
| 
 | ||||
|     @Rule | ||||
|     public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class); | ||||
| 
 | ||||
|     private CameraView camera; | ||||
|     private Camera1 controller; | ||||
|     private CameraListener listener; | ||||
|     private Task<Throwable> uiExceptionTask; | ||||
| 
 | ||||
|     @BeforeClass | ||||
|     public static void grant() { | ||||
|         grantPermissions(); | ||||
|     } | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         WorkerHandler.destroy(); | ||||
| 
 | ||||
|         ui(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 camera = new CameraView(rule.getActivity()) { | ||||
|                     @Override | ||||
|                     protected CameraController instantiateCameraController(CameraCallbacks callbacks) { | ||||
|                         controller = new Camera1(callbacks); | ||||
|                         return controller; | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 listener = mock(CameraListener.class); | ||||
|                 camera.addCameraListener(listener); | ||||
|                 rule.getActivity().inflate(camera); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Ensure that controller exceptions are thrown on this thread (not on the UI thread).
 | ||||
|         uiExceptionTask = new Task<>(true); | ||||
|         WorkerHandler crashThread = WorkerHandler.get("CrashThread"); | ||||
|         crashThread.getThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { | ||||
|             @Override | ||||
|             public void uncaughtException(Thread t, Throwable e) { | ||||
|                 uiExceptionTask.end(e); | ||||
|             } | ||||
|         }); | ||||
|         controller.mCrashHandler = crashThread.get(); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() throws Exception { | ||||
|         camera.stopCapturingVideo(); | ||||
|         camera.destroy(); | ||||
|         WorkerHandler.destroy(); | ||||
|     } | ||||
| 
 | ||||
|     private void waitForUiException() throws Throwable { | ||||
|         Throwable throwable = uiExceptionTask.await(2500); | ||||
|         if (throwable != null) throw throwable; | ||||
|     } | ||||
| 
 | ||||
|     private CameraOptions waitForOpen(boolean expectSuccess) { | ||||
|         camera.start(); | ||||
|         final Task<CameraOptions> open = new Task<>(true); | ||||
|         doEndTask(open, 0).when(listener).onCameraOpened(any(CameraOptions.class)); | ||||
|         CameraOptions result = open.await(4000); | ||||
|         if (expectSuccess) { | ||||
|             assertNotNull("Can open", result); | ||||
|         } else { | ||||
|             assertNull("Should not open", result); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     private void waitForClose(boolean expectSuccess) { | ||||
|         camera.stop(); | ||||
|         final Task<Boolean> close = new Task<>(true); | ||||
|         doEndTask(close, true).when(listener).onCameraClosed(); | ||||
|         Boolean result = close.await(4000); | ||||
|         if (expectSuccess) { | ||||
|             assertNotNull("Can close", result); | ||||
|         } else { | ||||
|             assertNull("Should not close", result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void waitForVideoEnd(boolean expectSuccess) { | ||||
|         final Task<Boolean> video = new Task<>(true); | ||||
|         doEndTask(video, true).when(listener).onVideoTaken(any(File.class)); | ||||
|         Boolean result = video.await(8000); | ||||
|         if (expectSuccess) { | ||||
|             assertNotNull("Should end video", result); | ||||
|         } else { | ||||
|             assertNull("Should not end video", result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private byte[] waitForPicture(boolean expectSuccess) { | ||||
|         final Task<byte[]> pic = new Task<>(true); | ||||
|         doEndTask(pic, 0).when(listener).onPictureTaken(any(byte[].class)); | ||||
|         byte[] result = pic.await(5000); | ||||
|         if (expectSuccess) { | ||||
|             assertNotNull("Can take picture", result); | ||||
|         } else { | ||||
|             assertNull("Should not take picture", result); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     private void waitForVideoStart() { | ||||
|         controller.mStartVideoTask.listen(); | ||||
|         camera.startCapturingVideo(null); | ||||
|         controller.mStartVideoTask.await(400); | ||||
|     } | ||||
| 
 | ||||
|     private void waitForVideoQuality(VideoQuality quality) { | ||||
|         controller.mVideoQualityTask.listen(); | ||||
|         camera.setVideoQuality(quality); | ||||
|         controller.mVideoQualityTask.await(400); | ||||
|     } | ||||
| 
 | ||||
|     //region test open/close
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOpenClose() throws Exception { | ||||
|         // Starting and stopping are hard to get since they happen on another thread.
 | ||||
|         assertEquals(controller.getState(), CameraController.STATE_STOPPED); | ||||
| 
 | ||||
|         waitForOpen(true); | ||||
|         assertEquals(controller.getState(), CameraController.STATE_STARTED); | ||||
| 
 | ||||
|         waitForClose(true); | ||||
|         assertEquals(controller.getState(), CameraController.STATE_STOPPED); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOpenTwice() { | ||||
|         waitForOpen(true); | ||||
|         waitForOpen(false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCloseTwice() { | ||||
|         waitForClose(false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     // This works great on the device but crashes often on the emulator.
 | ||||
|     // There must be something wrong with the emulated camera...
 | ||||
|     // Like stopPreview() and release() are not really sync calls?
 | ||||
|     public void testConcurrentCalls() throws Exception { | ||||
|         final CountDownLatch latch = new CountDownLatch(4); | ||||
|         doCountDown(latch).when(listener).onCameraOpened(any(CameraOptions.class)); | ||||
|         doCountDown(latch).when(listener).onCameraClosed(); | ||||
| 
 | ||||
|         camera.start(); | ||||
|         camera.stop(); | ||||
|         camera.start(); | ||||
|         camera.stop(); | ||||
| 
 | ||||
|         boolean did = latch.await(10, TimeUnit.SECONDS); | ||||
|         assertTrue("Handles concurrent calls to start & stop, " + latch.getCount(), did); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testStartInitializesOptions() { | ||||
|         assertNull(camera.getCameraOptions()); | ||||
|         assertNull(camera.getExtraProperties()); | ||||
|         waitForOpen(true); | ||||
|         assertNotNull(camera.getCameraOptions()); | ||||
|         assertNotNull(camera.getExtraProperties()); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region test Facing/SessionType
 | ||||
|     // Test things that should reset the camera.
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetFacing() throws Exception { | ||||
|         CameraOptions o = waitForOpen(true); | ||||
|         int size = o.getSupportedFacing().size(); | ||||
|         if (size > 1) { | ||||
|             // set facing should call stop and start again.
 | ||||
|             final CountDownLatch latch = new CountDownLatch(2); | ||||
|             doCountDown(latch).when(listener).onCameraOpened(any(CameraOptions.class)); | ||||
|             doCountDown(latch).when(listener).onCameraClosed(); | ||||
| 
 | ||||
|             camera.toggleFacing(); | ||||
| 
 | ||||
|             boolean did = latch.await(2, TimeUnit.SECONDS); | ||||
|             assertTrue("Handles setFacing while active", did); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetSessionType() throws Exception { | ||||
|         camera.setSessionType(SessionType.PICTURE); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         // set session type should call stop and start again.
 | ||||
|         final CountDownLatch latch = new CountDownLatch(2); | ||||
|         doCountDown(latch).when(listener).onCameraOpened(any(CameraOptions.class)); | ||||
|         doCountDown(latch).when(listener).onCameraClosed(); | ||||
| 
 | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
| 
 | ||||
|         boolean did = latch.await(2, TimeUnit.SECONDS); | ||||
|         assertTrue("Handles setSessionType while active", did); | ||||
|         assertEquals(camera.getSessionType(), SessionType.VIDEO); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region test Set Parameters
 | ||||
|     // When camera is open, parameters will be set only if supported.
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetZoom() { | ||||
|         CameraOptions options = waitForOpen(true); | ||||
| 
 | ||||
|         controller.mZoomTask.listen(); | ||||
|         float oldValue = camera.getZoom(); | ||||
|         float newValue = 0.65f; | ||||
|         camera.setZoom(newValue); | ||||
|         controller.mZoomTask.await(500); | ||||
| 
 | ||||
|         if (options.isZoomSupported()) { | ||||
|             assertEquals(newValue, camera.getZoom(), 0f); | ||||
|         } else { | ||||
|             assertEquals(oldValue, camera.getZoom(), 0f); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetExposureCorrection() { | ||||
|         CameraOptions options = waitForOpen(true); | ||||
| 
 | ||||
|         controller.mExposureCorrectionTask.listen(); | ||||
|         float oldValue = camera.getExposureCorrection(); | ||||
|         float newValue = options.getExposureCorrectionMaxValue(); | ||||
|         camera.setExposureCorrection(newValue); | ||||
|         controller.mExposureCorrectionTask.await(300); | ||||
| 
 | ||||
|         if (options.isExposureCorrectionSupported()) { | ||||
|             assertEquals(newValue, camera.getExposureCorrection(), 0f); | ||||
|         } else { | ||||
|             assertEquals(oldValue, camera.getExposureCorrection(), 0f); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetFlash() { | ||||
|         CameraOptions options = waitForOpen(true); | ||||
|         Flash[] values = Flash.values(); | ||||
|         Flash oldValue = camera.getFlash(); | ||||
|         for (Flash value : values) { | ||||
|             controller.mFlashTask.listen(); | ||||
|             camera.setFlash(value); | ||||
|             controller.mFlashTask.await(300); | ||||
|             if (options.supports(value)) { | ||||
|                 assertEquals(camera.getFlash(), value); | ||||
|                 oldValue = value; | ||||
|             } else { | ||||
|                 assertEquals(camera.getFlash(), oldValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetWhiteBalance() { | ||||
|         CameraOptions options = waitForOpen(true); | ||||
|         WhiteBalance[] values = WhiteBalance.values(); | ||||
|         WhiteBalance oldValue = camera.getWhiteBalance(); | ||||
|         for (WhiteBalance value : values) { | ||||
|             controller.mWhiteBalanceTask.listen(); | ||||
|             camera.setWhiteBalance(value); | ||||
|             controller.mWhiteBalanceTask.await(300); | ||||
|             if (options.supports(value)) { | ||||
|                 assertEquals(camera.getWhiteBalance(), value); | ||||
|                 oldValue = value; | ||||
|             } else { | ||||
|                 assertEquals(camera.getWhiteBalance(), oldValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetHdr() { | ||||
|         CameraOptions options = waitForOpen(true); | ||||
|         Hdr[] values = Hdr.values(); | ||||
|         Hdr oldValue = camera.getHdr(); | ||||
|         for (Hdr value : values) { | ||||
|             controller.mHdrTask.listen(); | ||||
|             camera.setHdr(value); | ||||
|             controller.mHdrTask.await(300); | ||||
|             if (options.supports(value)) { | ||||
|                 assertEquals(camera.getHdr(), value); | ||||
|                 oldValue = value; | ||||
|             } else { | ||||
|                 assertEquals(camera.getHdr(), oldValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetAudio() { | ||||
|         // TODO: when permissions are managed, check that Audio.ON triggers the audio permission
 | ||||
|         waitForOpen(true); | ||||
|         Audio[] values = Audio.values(); | ||||
|         for (Audio value : values) { | ||||
|             camera.setAudio(value); | ||||
|             assertEquals(camera.getAudio(), value); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetLocation() { | ||||
|         waitForOpen(true); | ||||
|         controller.mLocationTask.listen(); | ||||
|         camera.setLocation(10d, 2d); | ||||
|         controller.mLocationTask.await(300); | ||||
|         assertNotNull(camera.getLocation()); | ||||
|         assertEquals(camera.getLocation().getLatitude(), 10d, 0d); | ||||
|         assertEquals(camera.getLocation().getLongitude(), 2d, 0d); | ||||
|         // This also ensures there are no crashes when attaching it to camera parameters.
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetPlaySounds() { | ||||
|         controller.mPlaySoundsTask.listen(); | ||||
|         boolean oldValue = camera.getPlaySounds(); | ||||
|         boolean newValue = !oldValue; | ||||
|         camera.setPlaySounds(newValue); | ||||
|         controller.mPlaySoundsTask.await(300); | ||||
| 
 | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | ||||
|             Camera.CameraInfo info = new Camera.CameraInfo(); | ||||
|             Camera.getCameraInfo(camera.getCameraId(), info); | ||||
|             if (info.canDisableShutterSound) { | ||||
|                 assertEquals(newValue, camera.getPlaySounds()); | ||||
|             } | ||||
|         } else { | ||||
|             assertEquals(oldValue, camera.getPlaySounds()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region testSetVideoQuality
 | ||||
|     // This can be tricky because can trigger layout changes.
 | ||||
| 
 | ||||
|     @Test(expected = RuntimeException.class) | ||||
|     public void testSetVideoQuality_whileRecording() throws Throwable { | ||||
|         // Can't run on Travis, MediaRecorder not supported.
 | ||||
|         // Error while starting MediaRecorder. java.lang.RuntimeException: start failed.
 | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         waitForVideoQuality(VideoQuality.HIGHEST); | ||||
|         waitForOpen(true); | ||||
|         waitForVideoStart(); | ||||
|         waitForVideoQuality(VideoQuality.LOWEST); | ||||
|         waitForUiException(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetVideoQuality_whileInPictureSessionType() { | ||||
|         camera.setSessionType(SessionType.PICTURE); | ||||
|         waitForVideoQuality(VideoQuality.HIGHEST); | ||||
|         waitForOpen(true); | ||||
|         waitForVideoQuality(VideoQuality.LOWEST); | ||||
|         assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetVideoQuality_whileNotStarted() { | ||||
|         waitForVideoQuality(VideoQuality.HIGHEST); | ||||
|         assertEquals(camera.getVideoQuality(), VideoQuality.HIGHEST); | ||||
| 
 | ||||
|         waitForVideoQuality(VideoQuality.LOWEST); | ||||
|         assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetVideoQuality_shouldRecompute() { | ||||
|         // TODO:
 | ||||
|         // If video quality changes bring to a new capture size,
 | ||||
|         // this might bring to a new aspect ratio,
 | ||||
|         // which might bring to a new preview size. No idea how to test.
 | ||||
|         assertTrue(true); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region test startVideo
 | ||||
| 
 | ||||
|     @Test(expected = RuntimeException.class) | ||||
|     public void testStartVideo_whileInPictureMode() throws Throwable { | ||||
|         // Fails on Travis. Some emulators can't deal with MediaRecorder
 | ||||
|         // Error while starting MediaRecorder. java.lang.RuntimeException: start failed.
 | ||||
|         // as documented. This works locally though.
 | ||||
|         camera.setSessionType(SessionType.PICTURE); | ||||
|         waitForOpen(true); | ||||
|         waitForVideoStart(); | ||||
|         waitForUiException(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testStartEndVideo() { | ||||
|         // Fails on Travis. Some emulators can't deal with MediaRecorder,
 | ||||
|         // Error while starting MediaRecorder. java.lang.RuntimeException: start failed.
 | ||||
|         // as documented. This works locally though.
 | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         waitForOpen(true); | ||||
|         camera.startCapturingVideo(null, 4000); | ||||
|         waitForVideoEnd(true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEndVideo_withoutStarting() { | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         waitForOpen(true); | ||||
|         camera.stopCapturingVideo(); | ||||
|         waitForVideoEnd(false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEndVideo_withMaxSize() { | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         camera.setVideoMaxSize(500*1000); // 0.5 mb
 | ||||
|         waitForOpen(true); | ||||
|         waitForVideoStart(); | ||||
|         waitForVideoEnd(true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEndVideo_withMaxDuration() { | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         camera.setVideoMaxDuration(4000); | ||||
|         waitForOpen(true); | ||||
|         waitForVideoStart(); | ||||
|         waitForVideoEnd(true); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region startAutoFocus
 | ||||
|     // TODO: won't test onStopAutoFocus because that is not guaranteed to be called
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testStartAutoFocus() { | ||||
|         CameraOptions o = waitForOpen(true); | ||||
| 
 | ||||
|         final Task<PointF> focus = new Task<>(true); | ||||
|         doEndTask(focus, 0).when(listener).onFocusStart(any(PointF.class)); | ||||
| 
 | ||||
|         camera.startAutoFocus(1, 1); | ||||
|         PointF point = focus.await(300); | ||||
|         if (o.isAutoFocusSupported()) { | ||||
|             assertNotNull(point); | ||||
|             assertEquals(point, new PointF(1, 1)); | ||||
|         } else { | ||||
|             assertNull(point); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region capture
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCapturePicture_beforeStarted() { | ||||
|         camera.capturePicture(); | ||||
|         waitForPicture(false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCapturePicture_concurrentCalls() throws Exception { | ||||
|         // Second take should fail.
 | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         CountDownLatch latch = new CountDownLatch(2); | ||||
|         doCountDown(latch).when(listener).onPictureTaken(any(byte[].class)); | ||||
| 
 | ||||
|         camera.capturePicture(); | ||||
|         camera.capturePicture(); | ||||
|         boolean did = latch.await(4, TimeUnit.SECONDS); | ||||
|         assertFalse(did); | ||||
|         assertEquals(latch.getCount(), 1); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCapturePicture_size() throws Exception { | ||||
|         camera.setCropOutput(false); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         Size size = camera.getPictureSize(); | ||||
|         camera.capturePicture(); | ||||
|         byte[] jpeg = waitForPicture(true); | ||||
|         Bitmap b = CameraUtils.decodeBitmap(jpeg, Integer.MAX_VALUE, Integer.MAX_VALUE); | ||||
|         // Result can actually have swapped dimensions
 | ||||
|         // Which one, depends on factors including device physical orientation
 | ||||
|         assertTrue(b.getWidth() == size.getHeight() || b.getWidth() == size.getWidth()); | ||||
|         assertTrue(b.getHeight() == size.getHeight() || b.getHeight() == size.getWidth()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCaptureSnapshot_beforeStarted() { | ||||
|         camera.captureSnapshot(); | ||||
|         waitForPicture(false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCaptureSnapshot_concurrentCalls() throws Exception { | ||||
|         // Second take should fail.
 | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         CountDownLatch latch = new CountDownLatch(2); | ||||
|         doCountDown(latch).when(listener).onPictureTaken(any(byte[].class)); | ||||
| 
 | ||||
|         camera.captureSnapshot(); | ||||
|         camera.captureSnapshot(); | ||||
|         boolean did = latch.await(6, TimeUnit.SECONDS); | ||||
|         assertFalse(did); | ||||
|         assertEquals(1, latch.getCount()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCaptureSnapshot_size() throws Exception { | ||||
|         camera.setCropOutput(false); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         Size size = camera.getPreviewSize(); | ||||
|         camera.captureSnapshot(); | ||||
|         byte[] jpeg = waitForPicture(true); | ||||
|         Bitmap b = CameraUtils.decodeBitmap(jpeg, Integer.MAX_VALUE, Integer.MAX_VALUE); | ||||
|         // Result can actually have swapped dimensions
 | ||||
|         // Which one, depends on factors including device physical orientation
 | ||||
|         assertTrue(b.getWidth() == size.getHeight() || b.getWidth() == size.getWidth()); | ||||
|         assertTrue(b.getHeight() == size.getHeight() || b.getHeight() == size.getWidth()); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region Frame Processing
 | ||||
| 
 | ||||
|     private void assert30Frames(FrameProcessor mock) throws Exception { | ||||
|         // Expect 30 frames
 | ||||
|         CountDownLatch latch = new CountDownLatch(30); | ||||
|         doCountDown(latch).when(mock).process(any(Frame.class)); | ||||
|         boolean did = latch.await(4, TimeUnit.SECONDS); | ||||
|         assertTrue(did); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFrameProcessing_simple() throws Exception { | ||||
|         FrameProcessor processor = mock(FrameProcessor.class); | ||||
|         camera.addFrameProcessor(processor); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         assert30Frames(processor); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFrameProcessing_afterSnapshot() throws Exception { | ||||
|         FrameProcessor processor = mock(FrameProcessor.class); | ||||
|         camera.addFrameProcessor(processor); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         // In Camera1, snapshots will clear the preview callback
 | ||||
|         // Ensure we restore correctly
 | ||||
|         camera.captureSnapshot(); | ||||
|         waitForPicture(true); | ||||
| 
 | ||||
|         assert30Frames(processor); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFrameProcessing_afterRestart() throws Exception { | ||||
|         FrameProcessor processor = mock(FrameProcessor.class); | ||||
|         camera.addFrameProcessor(processor); | ||||
|         waitForOpen(true); | ||||
|         waitForClose(true); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         assert30Frames(processor); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFrameProcessing_afterVideo() throws Exception { | ||||
|         FrameProcessor processor = mock(FrameProcessor.class); | ||||
|         camera.addFrameProcessor(processor); | ||||
|         camera.setSessionType(SessionType.VIDEO); | ||||
|         waitForOpen(true); | ||||
| 
 | ||||
|         camera.startCapturingVideo(null, 2000); | ||||
|         waitForVideoEnd(true); | ||||
| 
 | ||||
|         assert30Frames(processor); | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| } | ||||
| @ -1,60 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class Mapper1Test extends BaseTest { | ||||
| 
 | ||||
|     private Mapper mapper = new Mapper.Mapper1(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testMap() { | ||||
|         assertEquals(mapper.map(Flash.OFF), Camera.Parameters.FLASH_MODE_OFF); | ||||
|         assertEquals(mapper.map(Flash.ON), Camera.Parameters.FLASH_MODE_ON); | ||||
|         assertEquals(mapper.map(Flash.AUTO), Camera.Parameters.FLASH_MODE_AUTO); | ||||
|         assertEquals(mapper.map(Flash.TORCH), Camera.Parameters.FLASH_MODE_TORCH); | ||||
| 
 | ||||
|         assertEquals(mapper.map(Facing.BACK), Camera.CameraInfo.CAMERA_FACING_BACK); | ||||
|         assertEquals(mapper.map(Facing.FRONT), Camera.CameraInfo.CAMERA_FACING_FRONT); | ||||
| 
 | ||||
|         assertEquals(mapper.map(Hdr.OFF), Camera.Parameters.SCENE_MODE_AUTO); | ||||
|         assertEquals(mapper.map(Hdr.ON), Camera.Parameters.SCENE_MODE_HDR); | ||||
| 
 | ||||
|         assertEquals(mapper.map(WhiteBalance.AUTO), Camera.Parameters.WHITE_BALANCE_AUTO); | ||||
|         assertEquals(mapper.map(WhiteBalance.DAYLIGHT), Camera.Parameters.WHITE_BALANCE_DAYLIGHT); | ||||
|         assertEquals(mapper.map(WhiteBalance.CLOUDY), Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); | ||||
|         assertEquals(mapper.map(WhiteBalance.INCANDESCENT), Camera.Parameters.WHITE_BALANCE_INCANDESCENT); | ||||
|         assertEquals(mapper.map(WhiteBalance.FLUORESCENT), Camera.Parameters.WHITE_BALANCE_FLUORESCENT); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testUnmap() { | ||||
|         assertEquals(Flash.OFF, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_OFF)); | ||||
|         assertEquals(Flash.ON, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_ON)); | ||||
|         assertEquals(Flash.AUTO, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_AUTO)); | ||||
|         assertEquals(Flash.TORCH, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_TORCH)); | ||||
| 
 | ||||
|         assertEquals(Facing.BACK, mapper.unmapFacing(Camera.CameraInfo.CAMERA_FACING_BACK)); | ||||
|         assertEquals(Facing.FRONT, mapper.unmapFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)); | ||||
| 
 | ||||
|         assertEquals(Hdr.OFF, mapper.unmapHdr(Camera.Parameters.SCENE_MODE_AUTO)); | ||||
|         assertEquals(Hdr.ON, mapper.unmapHdr(Camera.Parameters.SCENE_MODE_HDR)); | ||||
| 
 | ||||
|         assertEquals(WhiteBalance.AUTO, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO)); | ||||
|         assertEquals(WhiteBalance.DAYLIGHT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.CLOUDY, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.INCANDESCENT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_INCANDESCENT)); | ||||
|         assertEquals(WhiteBalance.FLUORESCENT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_FLUORESCENT)); | ||||
|     } | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.media.MediaRecorder; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class MapperTest extends BaseTest { | ||||
| 
 | ||||
|     private Mapper mapper = new Mapper() { | ||||
|         <T> T map(Flash flash) { return null; } | ||||
|         <T> T map(Facing facing) { return null; } | ||||
|         <T> T map(WhiteBalance whiteBalance) { return null; } | ||||
|         <T> T map(Hdr hdr) { return null; } | ||||
|         <T> Flash unmapFlash(T cameraConstant) { return null; } | ||||
|         <T> Facing unmapFacing(T cameraConstant) { return null; } | ||||
|         <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { return null; } | ||||
|         <T> Hdr unmapHdr(T cameraConstant) { return null; } | ||||
|     }; | ||||
| 
 | ||||
|     @Test | ||||
|     public void testMap() { | ||||
|         assertEquals(mapper.map(VideoCodec.DEVICE_DEFAULT), MediaRecorder.VideoEncoder.DEFAULT); | ||||
|         assertEquals(mapper.map(VideoCodec.H_263), MediaRecorder.VideoEncoder.H263); | ||||
|         assertEquals(mapper.map(VideoCodec.H_264), MediaRecorder.VideoEncoder.H264); | ||||
|     } | ||||
| } | ||||
| @ -1,132 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.PointF; | ||||
| import android.location.Location; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| public class MockCameraController extends CameraController { | ||||
| 
 | ||||
|     boolean mPictureCaptured; | ||||
|     boolean mFocusStarted; | ||||
|     boolean mZoomChanged; | ||||
|     boolean mExposureCorrectionChanged; | ||||
| 
 | ||||
|     MockCameraController(CameraView.CameraCallbacks callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     void setMockCameraOptions(CameraOptions options) { | ||||
|         mCameraOptions = options; | ||||
|     } | ||||
| 
 | ||||
|     void setMockPreviewSize(Size size) { | ||||
|         mPreviewSize = size; | ||||
|     } | ||||
| 
 | ||||
|     void mockStarted(boolean started) { | ||||
|         mState = started ? STATE_STARTED : STATE_STOPPED; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStart() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStop() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setZoom(float zoom, PointF[] points, boolean notify) { | ||||
|         mZoomValue = zoom; | ||||
|         mZoomChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setExposureCorrection(float EVvalue, float[] bounds, PointF[] points, boolean notify) { | ||||
|         mExposureCorrectionValue = EVvalue; | ||||
|         mExposureCorrectionChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFacing(Facing facing) { | ||||
|         mFacing = facing; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFlash(Flash flash) { | ||||
|         mFlash = flash; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setWhiteBalance(WhiteBalance whiteBalance) { | ||||
|         mWhiteBalance = whiteBalance; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setVideoQuality(VideoQuality videoQuality) { | ||||
|         mVideoQuality = videoQuality; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setSessionType(SessionType sessionType) { | ||||
|         mSessionType = sessionType; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setHdr(Hdr hdr) { | ||||
|         mHdr = hdr; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setAudio(Audio audio) { | ||||
|         mAudio = audio; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setLocation(Location location) { | ||||
|         mLocation = location; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void capturePicture() { | ||||
|         mPictureCaptured = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void captureSnapshot() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void startVideo(@NonNull File file) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void endVideo() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void startAutoFocus(@Nullable Gesture gesture, PointF point) { | ||||
|         mFocusStarted = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceChanged() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceAvailable() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBufferAvailable(byte[] buffer) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setPlaySounds(boolean playSounds) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.view.Surface; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| public class MockCameraPreview extends CameraPreview<View, Void> { | ||||
| 
 | ||||
|     MockCameraPreview(Context context, ViewGroup parent) { | ||||
|         super(context, parent, null); | ||||
|     } | ||||
| 
 | ||||
|     private boolean mCropping = false; | ||||
| 
 | ||||
|     public void setIsCropping(boolean crop) { | ||||
|         mCropping = crop; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     boolean isCropping() { | ||||
|         return mCropping; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected View onCreateView(Context context, ViewGroup parent) { | ||||
|         return new View(context); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     Surface getSurface() { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     Class<Void> getOutputClass() { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     Void getOutput() { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,99 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| import android.view.OrientationEventListener; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| import static org.mockito.Mockito.*; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class OrientationHelperTest extends BaseTest { | ||||
| 
 | ||||
|     private OrientationHelper helper; | ||||
|     private OrientationHelper.Callback callback; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         ui(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 callback = mock(OrientationHelper.Callback.class); | ||||
|                 helper = new OrientationHelper(context(), callback); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         callback = null; | ||||
|         helper = null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEnable() { | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertEquals(helper.getDisplayOffset(), -1); | ||||
|         assertEquals(helper.getDeviceOrientation(), -1); | ||||
| 
 | ||||
|         helper.enable(context()); | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
 | ||||
| 
 | ||||
|         // Ensure nothing bad if called twice.
 | ||||
|         helper.enable(context()); | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertNotEquals(helper.getDisplayOffset(), -1); | ||||
| 
 | ||||
|         helper.disable(); | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertEquals(helper.getDisplayOffset(), -1); | ||||
|         assertEquals(helper.getDeviceOrientation(), -1); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRotation() { | ||||
| 
 | ||||
|         // Sometimes (on some APIs) the helper will trigger an update to 0
 | ||||
|         // right after enabling. But that's fine for us, times(1) will be OK either way.
 | ||||
|         helper.enable(context()); | ||||
|         helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); | ||||
|         assertEquals(helper.getDeviceOrientation(), 0); | ||||
|         helper.mListener.onOrientationChanged(10); | ||||
|         assertEquals(helper.getDeviceOrientation(), 0); | ||||
|         helper.mListener.onOrientationChanged(-10); | ||||
|         assertEquals(helper.getDeviceOrientation(), 0); | ||||
|         helper.mListener.onOrientationChanged(44); | ||||
|         assertEquals(helper.getDeviceOrientation(), 0); | ||||
|         helper.mListener.onOrientationChanged(360); | ||||
|         assertEquals(helper.getDeviceOrientation(), 0); | ||||
| 
 | ||||
|         // Callback called just once.
 | ||||
|         verify(callback, times(1)).onDeviceOrientationChanged(0); | ||||
| 
 | ||||
|         helper.mListener.onOrientationChanged(90); | ||||
|         helper.mListener.onOrientationChanged(91); | ||||
|         assertEquals(helper.getDeviceOrientation(), 90); | ||||
|         verify(callback, times(1)).onDeviceOrientationChanged(90); | ||||
| 
 | ||||
|         helper.mListener.onOrientationChanged(180); | ||||
|         assertEquals(helper.getDeviceOrientation(), 180); | ||||
|         verify(callback, times(1)).onDeviceOrientationChanged(180); | ||||
| 
 | ||||
|         helper.mListener.onOrientationChanged(270); | ||||
|         assertEquals(helper.getDeviceOrientation(), 270); | ||||
|         verify(callback, times(1)).onDeviceOrientationChanged(270); | ||||
| 
 | ||||
|         // It is still 270 after ORIENTATION_UNKNOWN
 | ||||
|         helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); | ||||
|         assertEquals(helper.getDeviceOrientation(), 270); | ||||
|         verify(callback, times(1)).onDeviceOrientationChanged(270); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.PictureFormat; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class PictureResultTest extends BaseTest { | ||||
| 
 | ||||
|     private PictureResult.Stub stub = new PictureResult.Stub(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testResult() { | ||||
|         PictureFormat format = PictureFormat.JPEG; | ||||
|         int rotation = 90; | ||||
|         Size size = new Size(20, 120); | ||||
|         byte[] jpeg = new byte[]{2, 4, 1, 5, 2}; | ||||
|         Location location = Mockito.mock(Location.class); | ||||
|         boolean isSnapshot = true; | ||||
|         Facing facing = Facing.FRONT; | ||||
| 
 | ||||
|         stub.format = format; | ||||
|         stub.rotation = rotation; | ||||
|         stub.size = size; | ||||
|         stub.data = jpeg; | ||||
|         stub.location = location; | ||||
|         stub.facing = facing; | ||||
|         //noinspection ConstantConditions
 | ||||
|         stub.isSnapshot = isSnapshot; | ||||
| 
 | ||||
|         PictureResult result = new PictureResult(stub); | ||||
|         assertEquals(result.getFormat(), format); | ||||
|         assertEquals(result.getRotation(), rotation); | ||||
|         assertEquals(result.getSize(), size); | ||||
|         assertEquals(result.getData(), jpeg); | ||||
|         assertEquals(result.getLocation(), location); | ||||
|         //noinspection ConstantConditions
 | ||||
|         assertEquals(result.isSnapshot(), isSnapshot); | ||||
|         assertEquals(result.getFacing(), facing); | ||||
|     } | ||||
| } | ||||
| @ -1,66 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.espresso.ViewAction; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static android.support.test.espresso.matcher.ViewMatchers.withId; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class PinchGestureLayoutTest extends GestureLayoutTest<PinchGestureLayout> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected PinchGestureLayout create(Context context) { | ||||
|         return new PinchGestureLayout(context); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertEquals(layout.getGestureType(), Gesture.PINCH); | ||||
|         assertEquals(layout.getPoints().length, 2); | ||||
|         assertEquals(layout.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(layout.getPoints()[0].y, 0, 0); | ||||
|         assertEquals(layout.getPoints()[1].x, 0, 0); | ||||
|         assertEquals(layout.getPoints()[1].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: test pinch open
 | ||||
|     // TODO: test pinch close
 | ||||
|     // TODO: test pinch disabled
 | ||||
| 
 | ||||
|     // Possible approach: mimic pinch gesture and let espresso test.
 | ||||
|     // Too lazy to do this now, but it's possible.
 | ||||
|     // https://stackoverflow.com/questions/11523423/how-to-generate-zoom-pinch-gesture-for-testing-for-android
 | ||||
| 
 | ||||
|     public abstract class PinchViewAction implements ViewAction { | ||||
|     } | ||||
| 
 | ||||
|     private void testPinch(ViewAction action, boolean increasing) { | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         onLayout().perform(action); | ||||
|         Gesture found = touch.await(10000); | ||||
|         assertNotNull(found); | ||||
| 
 | ||||
|         // How will this move  our parameter?
 | ||||
|         float curr = 0.5f, min = 0f, max = 1f; | ||||
|         float newValue = layout.scaleValue(curr, min, max); | ||||
|         if (increasing) { | ||||
|             assertTrue(newValue > curr); | ||||
|             assertTrue(newValue <= max); | ||||
|         } else { | ||||
|             assertTrue(newValue < curr); | ||||
|             assertTrue(newValue >= min); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,92 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.espresso.ViewAction; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static android.support.test.espresso.action.ViewActions.click; | ||||
| import static android.support.test.espresso.action.ViewActions.swipeDown; | ||||
| import static android.support.test.espresso.action.ViewActions.swipeLeft; | ||||
| import static android.support.test.espresso.action.ViewActions.swipeRight; | ||||
| import static android.support.test.espresso.action.ViewActions.swipeUp; | ||||
| import static android.support.test.espresso.matcher.ViewMatchers.withId; | ||||
| import static junit.framework.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class ScrollGestureLayoutTest extends GestureLayoutTest<ScrollGestureLayout> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected ScrollGestureLayout create(Context context) { | ||||
|         return new ScrollGestureLayout(context); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertNull(layout.getGestureType()); | ||||
|         assertEquals(layout.getPoints().length, 2); | ||||
|         assertEquals(layout.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(layout.getPoints()[0].y, 0, 0); | ||||
|         assertEquals(layout.getPoints()[1].x, 0, 0); | ||||
|         assertEquals(layout.getPoints()[1].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollDisabled() { | ||||
|         layout.enable(false); | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         onLayout().perform(swipeUp()); | ||||
|         Gesture found = touch.await(500); | ||||
|         assertNull(found); | ||||
|     } | ||||
| 
 | ||||
|     private void testScroll(ViewAction scroll, Gesture expected, boolean increasing) { | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         onLayout().perform(scroll); | ||||
|         Gesture found = touch.await(500); | ||||
|         assertEquals(found, expected); | ||||
| 
 | ||||
|         // How will this move our parameter?
 | ||||
|         float curr = 0.5f, min = 0f, max = 1f; | ||||
|         float newValue = layout.scaleValue(curr, min, max); | ||||
|         if (increasing) { | ||||
|             assertTrue(newValue > curr); | ||||
|             assertTrue(newValue <= max); | ||||
|         } else { | ||||
|             assertTrue(newValue < curr); | ||||
|             assertTrue(newValue >= min); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollLeft() { | ||||
|         testScroll(swipeLeft(), Gesture.SCROLL_HORIZONTAL, false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollRight() { | ||||
|         testScroll(swipeRight(), Gesture.SCROLL_HORIZONTAL, true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollUp() { | ||||
|         testScroll(swipeUp(), Gesture.SCROLL_VERTICAL, true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollDown() { | ||||
|         testScroll(swipeDown(), Gesture.SCROLL_VERTICAL, false); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class SurfaceCameraPreviewTest extends PreviewTest { | ||||
| 
 | ||||
|     @Override | ||||
|     protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) { | ||||
|         return new SurfaceCameraPreview(context, parent, callback); | ||||
|     } | ||||
| } | ||||
| @ -1,77 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.espresso.action.GeneralClickAction; | ||||
| import android.support.test.espresso.action.GeneralLocation; | ||||
| import android.support.test.espresso.action.Press; | ||||
| import android.support.test.espresso.action.Tap; | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| import android.view.InputDevice; | ||||
| import android.view.MotionEvent; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static android.support.test.espresso.action.ViewActions.*; | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class TapGestureLayoutTest extends GestureLayoutTest<TapGestureLayout> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected TapGestureLayout create(Context context) { | ||||
|         return new TapGestureLayout(context); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertNull(layout.getGestureType()); | ||||
|         assertEquals(layout.getPoints().length, 1); | ||||
|         assertEquals(layout.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(layout.getPoints()[0].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testTap() { | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         GeneralClickAction a = new GeneralClickAction( | ||||
|                 Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER, | ||||
|                 InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); | ||||
|         onLayout().perform(a); | ||||
|         Gesture found = touch.await(500); | ||||
| 
 | ||||
|         assertEquals(found, Gesture.TAP); | ||||
|         Size size = rule.getActivity().getContentSize(); | ||||
|         assertEquals(layout.getPoints()[0].x, (size.getWidth() / 2f), 1f); | ||||
|         assertEquals(layout.getPoints()[0].y, (size.getHeight() / 2f), 1f); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testTapWhileDisabled() { | ||||
|         layout.enable(false); | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         onLayout().perform(click()); | ||||
|         Gesture found = touch.await(500); | ||||
|         assertNull(found); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testLongTap() { | ||||
|         touch.listen(); | ||||
|         touch.start(); | ||||
|         GeneralClickAction a = new GeneralClickAction( | ||||
|                 Tap.LONG, GeneralLocation.CENTER, Press.FINGER, | ||||
|                 InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); | ||||
|         onLayout().perform(a); | ||||
|         Gesture found = touch.await(500); | ||||
|         assertEquals(found, Gesture.LONG_TAP); | ||||
|         Size size = rule.getActivity().getContentSize(); | ||||
|         assertEquals(layout.getPoints()[0].x, (size.getWidth() / 2f), 1f); | ||||
|         assertEquals(layout.getPoints()[0].y, (size.getHeight() / 2f), 1f); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,146 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.controls.Audio; | ||||
| import com.otaliastudios.cameraview.controls.AudioCodec; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.VideoCodec; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileDescriptor; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class VideoResultTest extends BaseTest { | ||||
| 
 | ||||
|     private VideoResult.Stub stub = new VideoResult.Stub(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testResultWithFile() { | ||||
|         File file = Mockito.mock(File.class); | ||||
|         int rotation = 90; | ||||
|         Size size = new Size(20, 120); | ||||
|         VideoCodec videoCodec = VideoCodec.H_263; | ||||
|         AudioCodec audioCodec = AudioCodec.DEVICE_DEFAULT; | ||||
|         Location location = Mockito.mock(Location.class); | ||||
|         boolean isSnapshot = true; | ||||
|         int maxDuration = 1234; | ||||
|         long maxFileSize = 500000; | ||||
|         int reason = VideoResult.REASON_MAX_DURATION_REACHED; | ||||
|         int videoFrameRate = 30; | ||||
|         int videoBitRate = 300000; | ||||
|         int audioBitRate = 30000; | ||||
|         Audio audio = Audio.ON; | ||||
|         Facing facing = Facing.FRONT; | ||||
| 
 | ||||
|         stub.file = file; | ||||
|         stub.rotation = rotation; | ||||
|         stub.size = size; | ||||
|         stub.videoCodec = videoCodec; | ||||
|         stub.audioCodec = audioCodec; | ||||
|         stub.location = location; | ||||
|         stub.isSnapshot = isSnapshot; | ||||
|         stub.maxDuration = maxDuration; | ||||
|         stub.maxSize = maxFileSize; | ||||
|         stub.endReason = reason; | ||||
|         stub.videoFrameRate = videoFrameRate; | ||||
|         stub.videoBitRate = videoBitRate; | ||||
|         stub.audioBitRate = audioBitRate; | ||||
|         stub.audio = audio; | ||||
|         stub.facing = facing; | ||||
| 
 | ||||
|         VideoResult result = new VideoResult(stub); | ||||
|         assertEquals(result.getFile(), file); | ||||
|         assertEquals(result.getRotation(), rotation); | ||||
|         assertEquals(result.getSize(), size); | ||||
|         assertEquals(result.getVideoCodec(), videoCodec); | ||||
|         assertEquals(result.getAudioCodec(), audioCodec); | ||||
|         assertEquals(result.getLocation(), location); | ||||
|         assertEquals(result.isSnapshot(), isSnapshot); | ||||
|         assertEquals(result.getMaxSize(), maxFileSize); | ||||
|         assertEquals(result.getMaxDuration(), maxDuration); | ||||
|         assertEquals(result.getTerminationReason(), reason); | ||||
|         assertEquals(result.getVideoFrameRate(), videoFrameRate); | ||||
|         assertEquals(result.getVideoBitRate(), videoBitRate); | ||||
|         assertEquals(result.getAudioBitRate(), audioBitRate); | ||||
|         assertEquals(result.getAudio(), audio); | ||||
|         assertEquals(result.getFacing(), facing); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testResultWithFileDescriptor() { | ||||
|         FileDescriptor fileDescriptor = FileDescriptor.in; | ||||
|         int rotation = 90; | ||||
|         Size size = new Size(20, 120); | ||||
|         VideoCodec videoCodec = VideoCodec.H_263; | ||||
|         AudioCodec audioCodec = AudioCodec.DEVICE_DEFAULT; | ||||
|         Location location = Mockito.mock(Location.class); | ||||
|         boolean isSnapshot = true; | ||||
|         int maxDuration = 1234; | ||||
|         long maxFileSize = 500000; | ||||
|         int reason = VideoResult.REASON_MAX_DURATION_REACHED; | ||||
|         int videoFrameRate = 30; | ||||
|         int videoBitRate = 300000; | ||||
|         int audioBitRate = 30000; | ||||
|         Audio audio = Audio.ON; | ||||
|         Facing facing = Facing.FRONT; | ||||
| 
 | ||||
|         stub.fileDescriptor = fileDescriptor; | ||||
|         stub.rotation = rotation; | ||||
|         stub.size = size; | ||||
|         stub.videoCodec = videoCodec; | ||||
|         stub.audioCodec = audioCodec; | ||||
|         stub.location = location; | ||||
|         stub.isSnapshot = isSnapshot; | ||||
|         stub.maxDuration = maxDuration; | ||||
|         stub.maxSize = maxFileSize; | ||||
|         stub.endReason = reason; | ||||
|         stub.videoFrameRate = videoFrameRate; | ||||
|         stub.videoBitRate = videoBitRate; | ||||
|         stub.audioBitRate = audioBitRate; | ||||
|         stub.audio = audio; | ||||
|         stub.facing = facing; | ||||
| 
 | ||||
|         VideoResult result = new VideoResult(stub); | ||||
|         assertEquals(result.getFileDescriptor(), fileDescriptor); | ||||
|         assertEquals(result.getRotation(), rotation); | ||||
|         assertEquals(result.getSize(), size); | ||||
|         assertEquals(result.getVideoCodec(), videoCodec); | ||||
|         assertEquals(result.getAudioCodec(), audioCodec); | ||||
|         assertEquals(result.getLocation(), location); | ||||
|         assertEquals(result.isSnapshot(), isSnapshot); | ||||
|         assertEquals(result.getMaxSize(), maxFileSize); | ||||
|         assertEquals(result.getMaxDuration(), maxDuration); | ||||
|         assertEquals(result.getTerminationReason(), reason); | ||||
|         assertEquals(result.getVideoFrameRate(), videoFrameRate); | ||||
|         assertEquals(result.getVideoBitRate(), videoBitRate); | ||||
|         assertEquals(result.getAudioBitRate(), audioBitRate); | ||||
|         assertEquals(result.getAudio(), audio); | ||||
|         assertEquals(result.getFacing(), facing); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = RuntimeException.class) | ||||
|     public void testResultWithNoFile() { | ||||
|         VideoResult result = new VideoResult(stub); | ||||
|         result.getFile(); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = RuntimeException.class) | ||||
|     public void testResultWithNoFileDescriptor() { | ||||
|         VideoResult result = new VideoResult(stub); | ||||
|         result.getFileDescriptor(); | ||||
|     } | ||||
| } | ||||
| @ -1,39 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.support.test.filters.SmallTest; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class WorkerHandlerTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCache() { | ||||
|         WorkerHandler w1 = WorkerHandler.get("handler1"); | ||||
|         WorkerHandler w1a = WorkerHandler.get("handler1"); | ||||
|         WorkerHandler w2 = WorkerHandler.get("handler2"); | ||||
|         assertTrue(w1 == w1a); | ||||
|         assertFalse(w1 == w2); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testStaticRun() { | ||||
|         final Task<Boolean> task = new Task<>(true); | ||||
|         Runnable action = new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 task.end(true); | ||||
|             } | ||||
|         }; | ||||
|         WorkerHandler.run(action); | ||||
|         Boolean result = task.await(500); | ||||
|         assertNotNull(result); | ||||
|         assertTrue(result); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.controls.Engine; | ||||
| import com.otaliastudios.cameraview.frame.Frame; | ||||
| import com.otaliastudios.cameraview.frame.FrameProcessor; | ||||
| import com.otaliastudios.cameraview.tools.Op; | ||||
| import com.otaliastudios.cameraview.tools.Retry; | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.LargeTest; | ||||
| import androidx.test.filters.RequiresDevice; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| 
 | ||||
| /** | ||||
|  * These tests work great on real devices, and are the only way to test actual CameraEngine | ||||
|  * implementation - we really need to open the camera device. | ||||
|  * Unfortunately they fail unreliably on emulated devices, due to some bug with the | ||||
|  * emulated camera controller. | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @LargeTest | ||||
| // @RequiresDevice
 | ||||
| public class Camera1IntegrationTest extends CameraIntegrationTest<Camera1Engine> { | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Engine getEngine() { | ||||
|         return Engine.CAMERA1; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected long getMeteringTimeoutMillis() { | ||||
|         return Camera1Engine.AUTOFOCUS_END_DELAY_MILLIS; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void testFrameProcessing_maxSize() { | ||||
|         // Camera1Engine does not support different sizes.
 | ||||
|         // super.testFrameProcessing_maxSize();
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import android.hardware.camera2.CameraCharacteristics; | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Handler; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.controls.Engine; | ||||
| import com.otaliastudios.cameraview.engine.action.ActionHolder; | ||||
| import com.otaliastudios.cameraview.engine.action.BaseAction; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.LargeTest; | ||||
| import androidx.test.filters.RequiresDevice; | ||||
| 
 | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| 
 | ||||
| /** | ||||
|  * These tests work great on real devices, and are the only way to test actual CameraEngine | ||||
|  * implementation - we really need to open the camera device. | ||||
|  * Unfortunately they fail unreliably on emulated devices, due to some bug with the | ||||
|  * emulated camera controller. | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @LargeTest | ||||
| // @RequiresDevice
 | ||||
| public class Camera2IntegrationTest extends CameraIntegrationTest<Camera2Engine> { | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Engine getEngine() { | ||||
|         return Engine.CAMERA2; | ||||
|     } | ||||
| 
 | ||||
|     /* @Override | ||||
|     protected void onOpenSync() { | ||||
|         super.onOpenSync(); | ||||
|         // Extra wait for the first frame to be dispatched.
 | ||||
|         // This is because various classes require getLastResult to be non-null
 | ||||
|         // and that's typically the case in a real app.
 | ||||
|         final CountDownLatch latch = new CountDownLatch(1); | ||||
|         new BaseAction() { | ||||
|             @Override | ||||
|             public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                            @NonNull CaptureRequest request, | ||||
|                                            @NonNull TotalCaptureResult result) { | ||||
|                 super.onCaptureCompleted(holder, request, result); | ||||
|                 latch.countDown(); | ||||
|                 setState(STATE_COMPLETED); | ||||
|             } | ||||
|         }.start(controller); | ||||
|         try { latch.await(); } catch (InterruptedException ignore) {} | ||||
|     } */ | ||||
| 
 | ||||
|     @Override | ||||
|     protected long getMeteringTimeoutMillis() { | ||||
|         return Camera2Engine.METER_TIMEOUT; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * setMaxDuration can crash on legacy devices (most emulator are), and I don't see | ||||
|      * any way to fix this in code. They shouldn't use Camera2 at all. | ||||
|      * @return true if possible. | ||||
|      */ | ||||
|     @Override | ||||
|     protected boolean canSetVideoMaxDuration() { | ||||
|         if (!super.canSetVideoMaxDuration()) return false; | ||||
|         boolean shouldOpen = !camera.isOpened(); | ||||
|         if (shouldOpen) openSync(true); | ||||
|         boolean result = controller.readCharacteristic( | ||||
|                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) | ||||
|                 != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; | ||||
|         if (shouldOpen) closeSync(true); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void testFrameProcessing_freezeRelease() { | ||||
|         // Camera2 Frames are not freezable.
 | ||||
|         // super.testFrameProcessing_freezeRelease();
 | ||||
|     } | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -0,0 +1,215 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.PointF; | ||||
| import android.graphics.RectF; | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.tasks.Tasks; | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.PictureResult; | ||||
| import com.otaliastudios.cameraview.VideoResult; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.controls.PictureFormat; | ||||
| import com.otaliastudios.cameraview.engine.orchestrator.CameraState; | ||||
| import com.otaliastudios.cameraview.frame.ByteBufferFrameManager; | ||||
| import com.otaliastudios.cameraview.frame.FrameManager; | ||||
| import com.otaliastudios.cameraview.gesture.Gesture; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| import com.otaliastudios.cameraview.metering.MeteringRegions; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Callable; | ||||
| 
 | ||||
| public class MockCameraEngine extends CameraBaseEngine { | ||||
| 
 | ||||
|     public boolean mPictureCaptured; | ||||
|     public boolean mFocusStarted; | ||||
|     public boolean mZoomChanged; | ||||
|     public boolean mExposureCorrectionChanged; | ||||
| 
 | ||||
|     public MockCameraEngine(CameraEngine.Callback callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<CameraOptions> onStartEngine() { | ||||
|         return Tasks.forResult(mCameraOptions); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<Void> onStopEngine() { | ||||
|         return Tasks.forResult(null); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<Void> onStartBind() { | ||||
|         return Tasks.forResult(null); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<Void> onStopBind() { | ||||
|         return Tasks.forResult(null); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<Void> onStartPreview() { | ||||
|         return Tasks.forResult(null); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Task<Void> onStopPreview() { | ||||
|         return Tasks.forResult(null); | ||||
|     } | ||||
| 
 | ||||
|     public void setMockCameraOptions(CameraOptions options) { | ||||
|         mCameraOptions = options; | ||||
|     } | ||||
| 
 | ||||
|     public void setMockPreviewStreamSize(Size size) { | ||||
|         mPreviewStreamSize = size; | ||||
|     } | ||||
| 
 | ||||
|     public void setMockState(@NonNull CameraState state) { | ||||
|         Task<Void> change = getOrchestrator().scheduleStateChange(getState(), | ||||
|                 state, | ||||
|                 false, | ||||
|                 new Callable<Task<Void>>() { | ||||
|             @Override | ||||
|             public Task<Void> call() { | ||||
|                 return Tasks.forResult(null); | ||||
|             } | ||||
|         }); | ||||
|         try { | ||||
|             Tasks.await(change); | ||||
|         } catch (Exception ignore) {} | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setZoom(float zoom, @Nullable PointF[] points, boolean notify) { | ||||
|         mZoomValue = zoom; | ||||
|         mZoomChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nullable PointF[] points, boolean notify) { | ||||
|         mExposureCorrectionValue = EVvalue; | ||||
|         mExposureCorrectionChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setFlash(@NonNull Flash flash) { | ||||
|         mFlash = flash; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { | ||||
|         mWhiteBalance = whiteBalance; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setHdr(@NonNull Hdr hdr) { | ||||
|         mHdr = hdr; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setLocation(@Nullable Location location) { | ||||
|         mLocation = location; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setPictureFormat(@NonNull PictureFormat pictureFormat) { | ||||
|         mPictureFormat = pictureFormat; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setHasFrameProcessors(boolean hasFrameProcessors) { | ||||
|         mHasFrameProcessors = hasFrameProcessors; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setFrameProcessingFormat(int format) { | ||||
|         mFrameProcessingFormat = format; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void takePicture(@NonNull PictureResult.Stub stub) { | ||||
|         super.takePicture(stub); | ||||
|         mPictureCaptured = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio outputRatio, boolean doMetering) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onTakeVideo(@NonNull VideoResult.Stub stub) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull AspectRatio outputRatio) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPreviewStreamSizeChanged() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected List<Size> getPreviewStreamAvailableSizes() { | ||||
|         return new ArrayList<>(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected List<Size> getFrameProcessingAvailableSizes() { | ||||
|         return new ArrayList<>(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void startAutoFocus(@Nullable Gesture gesture, @NonNull MeteringRegions regions, @NonNull PointF legacyPoint) { | ||||
|         mFocusStarted = true; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected FrameManager instantiateFrameManager(int poolSize) { | ||||
|         return new ByteBufferFrameManager(poolSize, null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setPlaySounds(boolean playSounds) { } | ||||
| 
 | ||||
|     @Override | ||||
|     protected boolean collectCameraInfo(@NonNull Facing facing) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override public void setPreviewFrameRate(float previewFrameRate) { | ||||
|         mPreviewFrameRate = previewFrameRate; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,68 @@ | ||||
| package com.otaliastudios.cameraview.engine.mappers; | ||||
| 
 | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class Camera1MapperTest extends BaseTest { | ||||
| 
 | ||||
|     private Camera1Mapper mapper = Camera1Mapper.get(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testMap() { | ||||
|         assertEquals(mapper.mapFlash(Flash.OFF), Camera.Parameters.FLASH_MODE_OFF); | ||||
|         assertEquals(mapper.mapFlash(Flash.ON), Camera.Parameters.FLASH_MODE_ON); | ||||
|         assertEquals(mapper.mapFlash(Flash.AUTO), Camera.Parameters.FLASH_MODE_AUTO); | ||||
|         assertEquals(mapper.mapFlash(Flash.TORCH), Camera.Parameters.FLASH_MODE_TORCH); | ||||
| 
 | ||||
|         assertEquals(mapper.mapFacing(Facing.BACK), Camera.CameraInfo.CAMERA_FACING_BACK); | ||||
|         assertEquals(mapper.mapFacing(Facing.FRONT), Camera.CameraInfo.CAMERA_FACING_FRONT); | ||||
| 
 | ||||
|         assertEquals(mapper.mapHdr(Hdr.OFF), Camera.Parameters.SCENE_MODE_AUTO); | ||||
|         assertEquals(mapper.mapHdr(Hdr.ON), Camera.Parameters.SCENE_MODE_HDR); | ||||
| 
 | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.AUTO), Camera.Parameters.WHITE_BALANCE_AUTO); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.DAYLIGHT), Camera.Parameters.WHITE_BALANCE_DAYLIGHT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.CLOUDY), Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.INCANDESCENT), Camera.Parameters.WHITE_BALANCE_INCANDESCENT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.FLUORESCENT), Camera.Parameters.WHITE_BALANCE_FLUORESCENT); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testUnmap() { | ||||
|         assertEquals(Flash.OFF, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_OFF)); | ||||
|         assertEquals(Flash.ON, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_ON)); | ||||
|         assertEquals(Flash.AUTO, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_AUTO)); | ||||
|         assertEquals(Flash.TORCH, mapper.unmapFlash(Camera.Parameters.FLASH_MODE_TORCH)); | ||||
| 
 | ||||
|         assertEquals(Facing.BACK, mapper.unmapFacing(Camera.CameraInfo.CAMERA_FACING_BACK)); | ||||
|         assertEquals(Facing.FRONT, mapper.unmapFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)); | ||||
| 
 | ||||
|         assertEquals(Hdr.OFF, mapper.unmapHdr(Camera.Parameters.SCENE_MODE_AUTO)); | ||||
|         assertEquals(Hdr.ON, mapper.unmapHdr(Camera.Parameters.SCENE_MODE_HDR)); | ||||
| 
 | ||||
|         assertEquals(WhiteBalance.AUTO, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO)); | ||||
|         assertEquals(WhiteBalance.DAYLIGHT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.CLOUDY, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.INCANDESCENT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_INCANDESCENT)); | ||||
|         assertEquals(WhiteBalance.FLUORESCENT, mapper.unmapWhiteBalance(Camera.Parameters.WHITE_BALANCE_FLUORESCENT)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| package com.otaliastudios.cameraview.engine.mappers; | ||||
| 
 | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.util.Pair; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import static android.hardware.camera2.CameraMetadata.*; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class Camera2MapperTest extends BaseTest { | ||||
| 
 | ||||
|     private Camera2Mapper mapper = Camera2Mapper.get(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testMap() { | ||||
|         List<Pair<Integer, Integer>> values = mapper.mapFlash(Flash.OFF); | ||||
|         assertEquals(2, values.size()); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_OFF))); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_OFF))); | ||||
|         values = mapper.mapFlash(Flash.TORCH); | ||||
|         assertEquals(2, values.size()); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_TORCH))); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_TORCH))); | ||||
|         values = mapper.mapFlash(Flash.AUTO); | ||||
|         assertEquals(2, values.size()); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH, FLASH_MODE_OFF))); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE, FLASH_MODE_OFF))); | ||||
|         values = mapper.mapFlash(Flash.ON); | ||||
|         assertEquals(1, values.size()); | ||||
|         assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_ALWAYS_FLASH, FLASH_MODE_OFF))); | ||||
| 
 | ||||
|         assertEquals(mapper.mapFacing(Facing.BACK), LENS_FACING_BACK); | ||||
|         assertEquals(mapper.mapFacing(Facing.FRONT), LENS_FACING_FRONT); | ||||
| 
 | ||||
|         assertEquals(mapper.mapHdr(Hdr.OFF), CONTROL_SCENE_MODE_DISABLED); | ||||
|         assertEquals(mapper.mapHdr(Hdr.ON), CONTROL_SCENE_MODE_HDR); | ||||
| 
 | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.AUTO), CONTROL_AWB_MODE_AUTO); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.DAYLIGHT), CONTROL_AWB_MODE_DAYLIGHT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.CLOUDY), CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.INCANDESCENT), CONTROL_AWB_MODE_INCANDESCENT); | ||||
|         assertEquals(mapper.mapWhiteBalance(WhiteBalance.FLUORESCENT), CONTROL_AWB_MODE_FLUORESCENT); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testUnmap() { | ||||
|         Set<Flash> values; | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_OFF); | ||||
|         assertEquals(values.size(), 2); | ||||
|         assertTrue(values.contains(Flash.OFF)); | ||||
|         assertTrue(values.contains(Flash.TORCH)); | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_ON); | ||||
|         assertEquals(values.size(), 2); | ||||
|         assertTrue(values.contains(Flash.OFF)); | ||||
|         assertTrue(values.contains(Flash.TORCH)); | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_ON_ALWAYS_FLASH); | ||||
|         assertEquals(values.size(), 1); | ||||
|         assertTrue(values.contains(Flash.ON)); | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH); | ||||
|         assertEquals(values.size(), 1); | ||||
|         assertTrue(values.contains(Flash.AUTO)); | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); | ||||
|         assertEquals(values.size(), 1); | ||||
|         assertTrue(values.contains(Flash.AUTO)); | ||||
|         values = mapper.unmapFlash(CONTROL_AE_MODE_ON_EXTERNAL_FLASH); | ||||
|         assertEquals(values.size(), 0); | ||||
| 
 | ||||
|         assertEquals(Facing.BACK, mapper.unmapFacing(LENS_FACING_BACK)); | ||||
|         assertEquals(Facing.FRONT, mapper.unmapFacing(LENS_FACING_FRONT)); | ||||
| 
 | ||||
|         assertEquals(Hdr.OFF, mapper.unmapHdr(CONTROL_SCENE_MODE_DISABLED)); | ||||
|         assertEquals(Hdr.ON, mapper.unmapHdr(CONTROL_SCENE_MODE_HDR)); | ||||
| 
 | ||||
|         assertEquals(WhiteBalance.AUTO, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_AUTO)); | ||||
|         assertEquals(WhiteBalance.DAYLIGHT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.CLOUDY, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_CLOUDY_DAYLIGHT)); | ||||
|         assertEquals(WhiteBalance.INCANDESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_INCANDESCENT)); | ||||
|         assertEquals(WhiteBalance.FLUORESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_FLUORESCENT)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,172 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import android.opengl.GLES20; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseEglTest; | ||||
| import com.otaliastudios.cameraview.internal.GlTextureDrawer; | ||||
| import com.otaliastudios.opengl.program.GlTextureProgram; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class BaseFilterTest extends BaseEglTest { | ||||
| 
 | ||||
|     public static class TestFilter extends BaseFilter implements TwoParameterFilter { | ||||
| 
 | ||||
|         private float param1; | ||||
|         private float param2; | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public String getFragmentShader() { | ||||
|             return createDefaultFragmentShader(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setParameter1(float value) { | ||||
|             param1 = value; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setParameter2(float value) { | ||||
|             param2 = value; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public float getParameter1() { | ||||
|             return param1; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public float getParameter2() { | ||||
|             return param2; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private TestFilter filter; | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCreateDefaultFragmentShader() { | ||||
|         filter = new TestFilter(); | ||||
|         filter.fragmentTextureCoordinateName = "XXX"; | ||||
|         String defaultFragmentShader = filter.createDefaultFragmentShader(); | ||||
|         assertNotNull(defaultFragmentShader); | ||||
|         assertTrue(defaultFragmentShader.contains(filter.fragmentTextureCoordinateName)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCreateDefaultVertexShader() { | ||||
|         filter = new TestFilter(); | ||||
|         filter.vertexModelViewProjectionMatrixName = "AAA"; | ||||
|         filter.vertexPositionName = "BBB"; | ||||
|         filter.vertexTextureCoordinateName = "CCC"; | ||||
|         filter.vertexTransformMatrixName = "DDD"; | ||||
|         filter.fragmentTextureCoordinateName = "EEE"; | ||||
|         String defaultVertexShader = filter.createDefaultVertexShader(); | ||||
|         assertNotNull(defaultVertexShader); | ||||
|         assertTrue(defaultVertexShader.contains(filter.vertexModelViewProjectionMatrixName)); | ||||
|         assertTrue(defaultVertexShader.contains(filter.vertexPositionName)); | ||||
|         assertTrue(defaultVertexShader.contains(filter.vertexTextureCoordinateName)); | ||||
|         assertTrue(defaultVertexShader.contains(filter.vertexTransformMatrixName)); | ||||
|         assertTrue(defaultVertexShader.contains(filter.fragmentTextureCoordinateName)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnProgramCreate() { | ||||
|         filter = new TestFilter(); | ||||
|         int handle = GlTextureProgram.create(filter.getVertexShader(), filter.getFragmentShader()); | ||||
|         filter.onCreate(handle); | ||||
|         assertNotNull(filter.program); | ||||
|         filter.onDestroy(); | ||||
|         assertNull(filter.program); | ||||
|         GLES20.glDeleteProgram(handle); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDraw_whenInvalid() { | ||||
|         filter = spy(new TestFilter()); | ||||
|         float[] matrix = new float[16]; | ||||
|         filter.draw(0L, matrix); | ||||
|         verify(filter, never()).onPreDraw(0L, matrix); | ||||
|         verify(filter, never()).onDraw(0L); | ||||
|         verify(filter, never()).onPostDraw(0L); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDraw() { | ||||
|         // Use a drawer which cares about GL setup.
 | ||||
|         filter = spy(new TestFilter()); | ||||
|         GlTextureDrawer drawer = new GlTextureDrawer(); | ||||
|         drawer.setFilter(filter); | ||||
| 
 | ||||
|         float[] matrix = drawer.getTextureTransform(); | ||||
|         drawer.draw(0L); | ||||
|         verify(filter, times(1)).onPreDraw(0L, matrix); | ||||
|         verify(filter, times(1)).onDraw(0L); | ||||
|         verify(filter, times(1)).onPostDraw(0L); | ||||
| 
 | ||||
|         drawer.release(); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = RuntimeException.class) | ||||
|     public void testOnCopy_invalid() { | ||||
|         // Anonymous inner classes do not have a public constructor.
 | ||||
|         Filter filter = new BaseFilter() { | ||||
|             @NonNull | ||||
|             @Override | ||||
|             public String getFragmentShader() { | ||||
|                 return "whatever"; | ||||
|             } | ||||
|         }; | ||||
|         filter.copy(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnCopy() { | ||||
|         filter = new TestFilter(); | ||||
|         BaseFilter other = filter.copy(); | ||||
|         assertTrue(other instanceof TestFilter); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCopy_withSize() { | ||||
|         filter = new TestFilter(); | ||||
|         filter.setSize(WIDTH, HEIGHT); | ||||
|         BaseFilter other = filter.copy(); | ||||
|         assertEquals(WIDTH, other.size.getWidth()); | ||||
|         assertEquals(HEIGHT, other.size.getHeight()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCopy_withParameter1() { | ||||
|         filter = new TestFilter(); | ||||
|         filter.setParameter1(0.5F); | ||||
|         TestFilter other = (TestFilter) filter.copy(); | ||||
|         assertEquals(filter.getParameter1(), other.getParameter1(), 0.001F); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCopy_withParameter2() { | ||||
|         filter = new TestFilter(); | ||||
|         filter.setParameter2(0.5F); | ||||
|         TestFilter other = (TestFilter) filter.copy(); | ||||
|         assertEquals(filter.getParameter2(), other.getParameter2(), 0.001F); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static junit.framework.TestCase.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class FilterParserTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFallback() { | ||||
|         TypedArray array = mock(TypedArray.class); | ||||
|         when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(false); | ||||
|         when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(null); | ||||
|         FilterParser parser = new FilterParser(array); | ||||
|         assertNotNull(parser.getFilter()); | ||||
|         assertTrue(parser.getFilter() instanceof NoFilter); | ||||
|     } | ||||
|     @Test | ||||
|     public void testConstructor() { | ||||
|         TypedArray array = mock(TypedArray.class); | ||||
|         when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(true); | ||||
|         when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(MyFilter.class.getName()); | ||||
|         FilterParser parser = new FilterParser(array); | ||||
|         assertNotNull(parser.getFilter()); | ||||
|         assertTrue(parser.getFilter() instanceof MyFilter); | ||||
|     } | ||||
| 
 | ||||
|     public static class MyFilter extends BaseFilter { | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public String getFragmentShader() { | ||||
|             return createDefaultFragmentShader(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class FiltersTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testNewInstance() { | ||||
|         // At least tests that all our default filters have a no-args constructor.
 | ||||
|         Filters[] filtersArray = Filters.values(); | ||||
|         for (Filters filters : filtersArray) { | ||||
|             Filter filter = filters.newInstance(); | ||||
|             assertNotNull(filter); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,212 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import android.opengl.GLES20; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseEglTest; | ||||
| import com.otaliastudios.cameraview.filters.AutoFixFilter; | ||||
| import com.otaliastudios.cameraview.filters.BrightnessFilter; | ||||
| import com.otaliastudios.cameraview.filters.DuotoneFilter; | ||||
| import com.otaliastudios.cameraview.filters.VignetteFilter; | ||||
| import com.otaliastudios.cameraview.internal.GlTextureDrawer; | ||||
| import com.otaliastudios.opengl.program.GlProgram; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.invocation.InvocationOnMock; | ||||
| import org.mockito.stubbing.Answer; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.anyInt; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.atLeastOnce; | ||||
| import static org.mockito.Mockito.doAnswer; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class MultiFilterTest extends BaseEglTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testConstructor1() { | ||||
|         MultiFilter multiFilter = new MultiFilter( | ||||
|                 new DuotoneFilter(), | ||||
|                 new AutoFixFilter() | ||||
|         ); | ||||
|         assertEquals(2, multiFilter.filters.size()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testConstructor2() { | ||||
|         List<Filter> filters = new ArrayList<>(); | ||||
|         filters.add(new DuotoneFilter()); | ||||
|         filters.add(new AutoFixFilter()); | ||||
|         MultiFilter multiFilter = new MultiFilter(filters); | ||||
|         assertEquals(2, multiFilter.filters.size()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testAddFilter() { | ||||
|         MultiFilter multiFilter = new MultiFilter(); | ||||
|         assertEquals(0, multiFilter.filters.size()); | ||||
|         multiFilter.addFilter(new DuotoneFilter()); | ||||
|         assertEquals(1, multiFilter.filters.size()); | ||||
|         multiFilter.addFilter(new AutoFixFilter()); | ||||
|         assertEquals(2, multiFilter.filters.size()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testAddFilter_multi() { | ||||
|         MultiFilter multiFilter = new MultiFilter(new DuotoneFilter()); | ||||
|         assertEquals(1, multiFilter.filters.size()); | ||||
|         MultiFilter other = new MultiFilter( | ||||
|                 new AutoFixFilter(), | ||||
|                 new BrightnessFilter(), | ||||
|                 new VignetteFilter()); | ||||
|         multiFilter.addFilter(other); | ||||
|         assertEquals(4, multiFilter.filters.size()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetSize() { | ||||
|         DuotoneFilter filter = new DuotoneFilter(); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
|         MultiFilter.State state = multiFilter.states.get(filter); | ||||
|         assertNotNull(state); | ||||
|         assertNull(state.size); | ||||
|         multiFilter.setSize(WIDTH, HEIGHT); | ||||
|         assertNotNull(state.size); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCopy() { | ||||
|         DuotoneFilter filter = spy(new DuotoneFilter()); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
|         MultiFilter multiFilterCopy = (MultiFilter) multiFilter.copy(); | ||||
|         assertEquals(1, multiFilterCopy.filters.size()); | ||||
|         verify(filter, times(1)).onCopy(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testParameter1() { | ||||
|         DuotoneFilter filter = new DuotoneFilter(); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
|         float desired = 0.21582F; // whatever
 | ||||
|         multiFilter.setParameter1(desired); | ||||
|         assertEquals(desired, multiFilter.getParameter1(), 0.001F); | ||||
|         assertEquals(desired, filter.getParameter1(), 0.001F); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testParameter2() { | ||||
|         DuotoneFilter filter = new DuotoneFilter(); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
|         float desired = 0.21582F; // whatever
 | ||||
|         multiFilter.setParameter2(desired); | ||||
|         assertEquals(desired, multiFilter.getParameter2(), 0.001F); | ||||
|         assertEquals(desired, filter.getParameter2(), 0.001F); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnCreate_isLazy() { | ||||
|         DuotoneFilter filter = spy(new DuotoneFilter()); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
| 
 | ||||
|         int program = GlProgram.create(multiFilter.getVertexShader(), | ||||
|                 multiFilter.getFragmentShader()); | ||||
|         multiFilter.onCreate(program); | ||||
|         verify(filter, never()).onCreate(anyInt()); | ||||
| 
 | ||||
|         multiFilter.onDestroy(); | ||||
|         GLES20.glDeleteProgram(program); | ||||
|         verify(filter, never()).onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDraw_simple() { | ||||
|         DuotoneFilter filter = spy(new DuotoneFilter()); | ||||
|         MultiFilter multiFilter = new MultiFilter(filter); | ||||
|         multiFilter.setSize(WIDTH, HEIGHT); | ||||
|         GlTextureDrawer drawer = new GlTextureDrawer(); | ||||
|         drawer.setFilter(multiFilter); | ||||
|         float[] matrix = drawer.getTextureTransform(); | ||||
|         drawer.draw(0L); | ||||
|         drawer.release(); | ||||
| 
 | ||||
|         // The child should have experienced the whole lifecycle.
 | ||||
|         verify(filter, atLeastOnce()).getVertexShader(); | ||||
|         verify(filter, atLeastOnce()).getFragmentShader(); | ||||
|         verify(filter, atLeastOnce()).setSize(anyInt(), anyInt()); | ||||
|         verify(filter, times(1)).onCreate(anyInt()); | ||||
|         verify(filter, times(1)).draw(0L, matrix); | ||||
|         verify(filter, times(1)).onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDraw_multi() { | ||||
|         // Want to test that when filter1 is drawn, the current framebuffer is a
 | ||||
|         // non-default one. When filter2 is drawn, the current framebuffer is 0.
 | ||||
|         final DuotoneFilter filter1 = spy(new DuotoneFilter()); | ||||
|         final DuotoneFilter filter2 = spy(new DuotoneFilter()); | ||||
|         final MultiFilter multiFilter = new MultiFilter(filter1, filter2); | ||||
|         multiFilter.setSize(WIDTH, HEIGHT); | ||||
|         GlTextureDrawer drawer = new GlTextureDrawer(); | ||||
|         drawer.setFilter(multiFilter); | ||||
|         float[] matrix = drawer.getTextureTransform(); | ||||
|         final int[] result = new int[1]; | ||||
| 
 | ||||
|         doAnswer(new Answer() { | ||||
|             @Override | ||||
|             public Object answer(InvocationOnMock invocation) { | ||||
|                 MultiFilter.State state = multiFilter.states.get(filter1); | ||||
|                 assertNotNull(state); | ||||
|                 assertTrue(state.isProgramCreated); | ||||
|                 assertTrue(state.isFramebufferCreated); | ||||
| 
 | ||||
|                 GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); | ||||
|                 // assertTrue(result[0] != 0);
 | ||||
|                 return null; | ||||
|             } | ||||
|         }).when(filter1).draw(0L, matrix); | ||||
| 
 | ||||
|         // Note: second filter is drawn with the identity matrix!
 | ||||
|         doAnswer(new Answer() { | ||||
|             @Override | ||||
|             public Object answer(InvocationOnMock invocation) { | ||||
|                 // The last filter has no FBO / texture.
 | ||||
|                 MultiFilter.State state = multiFilter.states.get(filter2); | ||||
|                 assertNotNull(state); | ||||
|                 assertTrue(state.isProgramCreated); | ||||
|                 assertFalse(state.isFramebufferCreated); | ||||
| 
 | ||||
|                 GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); | ||||
|                 assertEquals(0, result[0]); | ||||
|                 return null; | ||||
| 
 | ||||
|             } | ||||
|         }).when(filter2).draw(eq(0L), any(float[].class)); | ||||
| 
 | ||||
|         drawer.draw(0L); | ||||
|         drawer.release(); | ||||
| 
 | ||||
|         // Verify that both are drawn.
 | ||||
|         verify(filter1, times(1)).draw(0L, matrix); | ||||
|         verify(filter2, times(1)).draw(eq(0L), any(float[].class)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class NoFilterTest extends BaseTest { | ||||
| 
 | ||||
|     public static class DummyFilter extends BaseFilter { | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public String getFragmentShader() { | ||||
|             return "whatever"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetFragmentShader() { | ||||
|         NoFilter filter = new NoFilter(); | ||||
|         String defaultFragmentShader = new DummyFilter().createDefaultFragmentShader(); | ||||
|         assertEquals(defaultFragmentShader, filter.getFragmentShader()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.otaliastudios.cameraview.filter; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static junit.framework.TestCase.assertNotNull; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class SimpleFilterTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetFragmentShader() { | ||||
|         String shader = "shader"; | ||||
|         SimpleFilter filter = new SimpleFilter(shader); | ||||
|         assertEquals(shader, filter.getFragmentShader()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCopy() { | ||||
|         String shader = "shader"; | ||||
|         SimpleFilter filter = new SimpleFilter(shader); | ||||
|         BaseFilter copy = filter.copy(); | ||||
|         assertTrue(copy instanceof SimpleFilter); | ||||
|         SimpleFilter simpleCopy = (SimpleFilter) copy; | ||||
|         assertEquals(shader, simpleCopy.getFragmentShader()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,112 @@ | ||||
| package com.otaliastudios.cameraview.frame; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.ImageFormat; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.engine.offset.Angles; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.reset; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class ByteBufferFrameManagerTest extends BaseTest { | ||||
| 
 | ||||
|     private final Angles angles = new Angles(); | ||||
|     private ByteBufferFrameManager.BufferCallback callback; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         callback = mock(ByteBufferFrameManager.BufferCallback.class); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         callback = null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testAllocate() { | ||||
|         ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
|         verify(callback, times(1)).onBufferAvailable(any(byte[].class)); | ||||
|         reset(callback); | ||||
| 
 | ||||
|         manager = new ByteBufferFrameManager(5, callback); | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
|         verify(callback, times(5)).onBufferAvailable(any(byte[].class)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnFrameReleased_alreadyFull() { | ||||
|         ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
|         int length = manager.getFrameBytes(); | ||||
| 
 | ||||
|         Frame frame1 = manager.getFrame(new byte[length], 0); | ||||
|         assertNotNull(frame1); | ||||
|         // Since frame1 is already taken and poolSize = 1, getFrame() would return null.
 | ||||
|         // To create a new frame, freeze the first one.
 | ||||
|         Frame frame2 = frame1.freeze(); | ||||
|         // Now release the first frame so it goes back into the pool.
 | ||||
|         manager.onFrameReleased(frame1, (byte[]) frame1.getData()); | ||||
|         reset(callback); | ||||
|         // Release the second. The pool is already full, so onBufferAvailable should not be called
 | ||||
|         // since this Frame instance will NOT be reused.
 | ||||
|         manager.onFrameReleased(frame2, (byte[]) frame2.getData()); | ||||
|         verify(callback, never()).onBufferAvailable((byte[]) frame2.getData()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnFrameReleased_sameLength() { | ||||
|         ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
|         int length = manager.getFrameBytes(); | ||||
| 
 | ||||
|         // A camera preview frame comes. Request a frame.
 | ||||
|         byte[] picture = new byte[length]; | ||||
|         Frame frame = manager.getFrame(picture, 0); | ||||
|         assertNotNull(frame); | ||||
| 
 | ||||
|         // Release the frame and ensure that onBufferAvailable is called.
 | ||||
|         reset(callback); | ||||
|         manager.onFrameReleased(frame, (byte[]) frame.getData()); | ||||
|         verify(callback, times(1)).onBufferAvailable(picture); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnFrameReleased_differentLength() { | ||||
|         ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
|         int length = manager.getFrameBytes(); | ||||
| 
 | ||||
|         // A camera preview frame comes. Request a frame.
 | ||||
|         byte[] picture = new byte[length]; | ||||
|         Frame frame = manager.getFrame(picture, 0); | ||||
|         assertNotNull(frame); | ||||
| 
 | ||||
|         // Don't release the frame. Change the allocation size.
 | ||||
|         manager.setUp(ImageFormat.NV16, new Size(15, 15), angles); | ||||
| 
 | ||||
|         // Now release the old frame and ensure that onBufferAvailable is NOT called,
 | ||||
|         // because the released data has wrong length.
 | ||||
|         manager.onFrameReleased(frame, (byte[]) frame.getData()); | ||||
|         reset(callback); | ||||
|         verify(callback, never()).onBufferAvailable(picture); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,73 @@ | ||||
| package com.otaliastudios.cameraview.frame; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.ImageFormat; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.engine.offset.Angles; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class FrameManagerTest extends BaseTest { | ||||
| 
 | ||||
|     private final Angles angles = new Angles(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFrameRecycling() { | ||||
|         // A 1-pool manager will always recycle the same frame.
 | ||||
|         FrameManager<String> manager = new FrameManager<String>(1, String.class) { | ||||
|             @Override | ||||
|             protected void onFrameDataReleased(@NonNull String data, boolean recycled) { } | ||||
| 
 | ||||
|             @NonNull | ||||
|             @Override | ||||
|             protected String onCloneFrameData(@NonNull String data) { | ||||
|                 return data; | ||||
|             } | ||||
|         }; | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
| 
 | ||||
|         Frame first = manager.getFrame("foo", 0); | ||||
|         assertNotNull(first); | ||||
|         first.release(); | ||||
|         Frame second = manager.getFrame("bar", 0); | ||||
|         assertNotNull(second); | ||||
|         second.release(); | ||||
|         assertEquals(first, second); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetFrame() { | ||||
|         FrameManager<String> manager = new FrameManager<String>(1, String.class) { | ||||
|             @Override | ||||
|             protected void onFrameDataReleased(@NonNull String data, boolean recycled) { } | ||||
| 
 | ||||
|             @NonNull | ||||
|             @Override | ||||
|             protected String onCloneFrameData(@NonNull String data) { | ||||
|                 return data; | ||||
|             } | ||||
|         }; | ||||
|         manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); | ||||
| 
 | ||||
|         Frame first = manager.getFrame("foo", 0); | ||||
|         assertNotNull(first); | ||||
|         Frame second = manager.getFrame("bar", 0); | ||||
|         assertNull(second); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,90 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.espresso.ViewInteraction; | ||||
| import androidx.test.espresso.matcher.RootMatchers; | ||||
| import androidx.test.rule.ActivityTestRule; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.FrameLayout; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.TestActivity; | ||||
| import com.otaliastudios.cameraview.tools.Op; | ||||
| 
 | ||||
| import org.hamcrest.Matchers; | ||||
| import org.junit.Before; | ||||
| import org.junit.Rule; | ||||
| 
 | ||||
| import static androidx.test.espresso.Espresso.onView; | ||||
| 
 | ||||
| @TargetApi(17) | ||||
| public abstract class GestureFinderTest<T extends GestureFinder> extends BaseTest { | ||||
| 
 | ||||
|     protected abstract T createFinder(@NonNull GestureFinder.Controller controller); | ||||
| 
 | ||||
|     @Rule | ||||
|     public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected T finder; | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected Op<Gesture> touchOp; | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected ViewGroup layout; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 TestActivity a = rule.getActivity(); | ||||
|                 layout = new FrameLayout(a); | ||||
|                 finder = createFinder(new Controller()); | ||||
|                 finder.setActive(true); | ||||
|                 a.inflate(layout); | ||||
| 
 | ||||
|                 touchOp = new Op<>(false); | ||||
|                 layout.setOnTouchListener(new View.OnTouchListener() { | ||||
|                     @Override | ||||
|                     public boolean onTouch(View view, MotionEvent motionEvent) { | ||||
|                         boolean found = finder.onTouchEvent(motionEvent); | ||||
|                         if (found) touchOp.controller().end(finder.getGesture()); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final ViewInteraction onLayout() { | ||||
|         return onView(Matchers.<View>is(layout)) | ||||
|                 .inRoot(RootMatchers.withDecorView( | ||||
|                         Matchers.is(rule.getActivity().getWindow().getDecorView()))); | ||||
|     } | ||||
| 
 | ||||
|     private class Controller implements GestureFinder.Controller { | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public Context getContext() { | ||||
|             return layout.getContext(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int getWidth() { | ||||
|             return layout.getWidth(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int getHeight() { | ||||
|             return layout.getHeight(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,71 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.espresso.ViewAction; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| /** | ||||
|  * On API 26 these tests fail during Espresso's inRoot() - the window never gains focus. | ||||
|  * This might be due to a system popup or something similar. | ||||
|  */ | ||||
| @SdkExclude(minSdkVersion = 26, maxSdkVersion = 26) | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class PinchGestureFinderTest extends GestureFinderTest<PinchGestureFinder> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected PinchGestureFinder createFinder(@NonNull GestureFinder.Controller controller) { | ||||
|         return new PinchGestureFinder(controller); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertEquals(finder.getGesture(), Gesture.PINCH); | ||||
|         assertEquals(finder.getPoints().length, 2); | ||||
|         assertEquals(finder.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(finder.getPoints()[0].y, 0, 0); | ||||
|         assertEquals(finder.getPoints()[1].x, 0, 0); | ||||
|         assertEquals(finder.getPoints()[1].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: test pinch open
 | ||||
|     // TODO: test pinch close
 | ||||
|     // TODO: test pinch disabled
 | ||||
| 
 | ||||
|     // Possible approach: mimic pinch gesture and let espresso test.
 | ||||
|     // Too lazy to do this now, but it's possible.
 | ||||
|     // https://stackoverflow.com/questions/11523423/how-to-generate-zoom-pinch-gesture-for-testing-for-android
 | ||||
| 
 | ||||
|     public abstract class PinchViewAction implements ViewAction { | ||||
|     } | ||||
| 
 | ||||
|     private void testPinch(ViewAction action, boolean increasing) { | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         onLayout().perform(action); | ||||
|         Gesture found = touchOp.await(10000); | ||||
|         assertNotNull(found); | ||||
| 
 | ||||
|         // How will this move  our parameter?
 | ||||
|         float curr = 0.5f, min = 0f, max = 1f; | ||||
|         float newValue = finder.computeValue(curr, min, max); | ||||
|         if (increasing) { | ||||
|             assertTrue(newValue > curr); | ||||
|             assertTrue(newValue <= max); | ||||
|         } else { | ||||
|             assertTrue(newValue < curr); | ||||
|             assertTrue(newValue >= min); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,98 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.espresso.ViewAction; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static androidx.test.espresso.action.ViewActions.swipeDown; | ||||
| import static androidx.test.espresso.action.ViewActions.swipeLeft; | ||||
| import static androidx.test.espresso.action.ViewActions.swipeRight; | ||||
| import static androidx.test.espresso.action.ViewActions.swipeUp; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| /** | ||||
|  * On API 26 these tests fail during Espresso's inRoot() - the window never gains focus. | ||||
|  * This might be due to a system popup or something similar. | ||||
|  */ | ||||
| @SdkExclude(minSdkVersion = 26, maxSdkVersion = 26) | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFinder> { | ||||
| 
 | ||||
|     private final static long WAIT = 2000; // 500 was too short
 | ||||
| 
 | ||||
|     @Override | ||||
|     protected ScrollGestureFinder createFinder(@NonNull GestureFinder.Controller controller) { | ||||
|         return new ScrollGestureFinder(controller); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertNull(finder.mType); | ||||
|         assertEquals(finder.getPoints().length, 2); | ||||
|         assertEquals(finder.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(finder.getPoints()[0].y, 0, 0); | ||||
|         assertEquals(finder.getPoints()[1].x, 0, 0); | ||||
|         assertEquals(finder.getPoints()[1].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollDisabled() { | ||||
|         finder.setActive(false); | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         onLayout().perform(swipeUp()); | ||||
|         Gesture found = touchOp.await(WAIT); | ||||
|         assertNull(found); | ||||
|     } | ||||
| 
 | ||||
|     private void testScroll(ViewAction scroll, Gesture expected, boolean increasing) { | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         onLayout().perform(scroll); | ||||
|         Gesture found = touchOp.await(WAIT); | ||||
|         assertEquals(found, expected); | ||||
| 
 | ||||
|         // How will this move our parameter?
 | ||||
|         float curr = 0.5f, min = 0f, max = 1f; | ||||
|         float newValue = finder.computeValue(curr, min, max); | ||||
|         if (increasing) { | ||||
|             assertTrue(newValue >= curr); | ||||
|             assertTrue(newValue <= max); | ||||
|         } else { | ||||
|             assertTrue(newValue <= curr); | ||||
|             assertTrue(newValue >= min); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollLeft() { | ||||
|         testScroll(swipeLeft(), Gesture.SCROLL_HORIZONTAL, false); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollRight() { | ||||
|         testScroll(swipeRight(), Gesture.SCROLL_HORIZONTAL, true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollUp() { | ||||
|         testScroll(swipeUp(), Gesture.SCROLL_VERTICAL, true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testScrollDown() { | ||||
|         testScroll(swipeDown(), Gesture.SCROLL_VERTICAL, false); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.espresso.action.GeneralClickAction; | ||||
| import androidx.test.espresso.action.GeneralLocation; | ||||
| import androidx.test.espresso.action.Press; | ||||
| import androidx.test.espresso.action.Tap; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.InputDevice; | ||||
| import android.view.MotionEvent; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static androidx.test.espresso.action.ViewActions.*; | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * On API 26 these tests fail during Espresso's inRoot() - the window never gains focus. | ||||
|  * This might be due to a system popup or something similar. | ||||
|  */ | ||||
| @SdkExclude(minSdkVersion = 26, maxSdkVersion = 26) | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class TapGestureFinderTest extends GestureFinderTest<TapGestureFinder> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected TapGestureFinder createFinder(@NonNull GestureFinder.Controller controller) { | ||||
|         return new TapGestureFinder(controller); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDefaults() { | ||||
|         assertNull(finder.mType); | ||||
|         assertEquals(finder.getPoints().length, 1); | ||||
|         assertEquals(finder.getPoints()[0].x, 0, 0); | ||||
|         assertEquals(finder.getPoints()[0].y, 0, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testTap() { | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         GeneralClickAction a = new GeneralClickAction( | ||||
|                 Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER, | ||||
|                 InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); | ||||
|         onLayout().perform(a); | ||||
|         Gesture found = touchOp.await(500); | ||||
| 
 | ||||
|         assertEquals(found, Gesture.TAP); | ||||
|         Size size = rule.getActivity().getContentSize(); | ||||
|         assertEquals(finder.getPoints()[0].x, (size.getWidth() / 2f), 1f); | ||||
|         assertEquals(finder.getPoints()[0].y, (size.getHeight() / 2f), 1f); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testTapWhileDisabled() { | ||||
|         finder.setActive(false); | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         onLayout().perform(click()); | ||||
|         Gesture found = touchOp.await(500); | ||||
|         assertNull(found); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testLongTap() { | ||||
|         touchOp.listen(); | ||||
|         touchOp.controller().start(); | ||||
|         GeneralClickAction a = new GeneralClickAction( | ||||
|                 Tap.LONG, GeneralLocation.CENTER, Press.FINGER, | ||||
|                 InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); | ||||
|         onLayout().perform(a); | ||||
|         Gesture found = touchOp.await(500); | ||||
|         assertEquals(found, Gesture.LONG_TAP); | ||||
|         Size size = rule.getActivity().getContentSize(); | ||||
|         assertEquals(finder.getPoints()[0].x, (size.getWidth() / 2f), 1f); | ||||
|         assertEquals(finder.getPoints()[0].y, (size.getHeight() / 2f), 1f); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.media.CamcorderProfile; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.CameraUtils; | ||||
| import com.otaliastudios.cameraview.internal.CamcorderProfiles; | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class CamcorderProfilesTest extends BaseTest { | ||||
| 
 | ||||
|     private String getCameraId() { | ||||
|         if (CameraUtils.hasCameras(getContext())) { | ||||
|             return "0"; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testInvalidCameraReturnsLowest() { | ||||
|         CamcorderProfile invalid = CamcorderProfiles.get("invalid", new Size(100, 100)); | ||||
|         CamcorderProfile lowest = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); | ||||
|         assertEquals(lowest.videoFrameWidth, invalid.videoFrameWidth); | ||||
|         assertEquals(lowest.videoFrameHeight, invalid.videoFrameHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * For some reason this fails on emulator 26. | ||||
|      */ | ||||
|     @SdkExclude(minSdkVersion = 26, maxSdkVersion = 26) | ||||
|     @Test | ||||
|     public void testGet() { | ||||
|         String cameraId = getCameraId(); | ||||
|         if (cameraId == null) return; | ||||
|         int cameraIdInt = Integer.parseInt(cameraId); | ||||
| 
 | ||||
|         // Not much we can test. Let's just ask for lowest and highest.
 | ||||
|         CamcorderProfile low = CamcorderProfiles.get(cameraId, new Size(1, 1)); | ||||
|         CamcorderProfile high = CamcorderProfiles.get(cameraId, new Size(Integer.MAX_VALUE, Integer.MAX_VALUE)); | ||||
| 
 | ||||
|         // Compare with lowest
 | ||||
|         CamcorderProfile lowest = CamcorderProfile.get(cameraIdInt, CamcorderProfile.QUALITY_LOW); | ||||
|         CamcorderProfile highest = CamcorderProfile.get(cameraIdInt, CamcorderProfile.QUALITY_HIGH); | ||||
|         assertEquals(lowest.videoFrameWidth, low.videoFrameWidth); | ||||
|         assertEquals(lowest.videoFrameHeight, low.videoFrameHeight); | ||||
|         assertEquals(highest.videoFrameWidth, high.videoFrameWidth); | ||||
|         assertEquals(highest.videoFrameHeight, high.videoFrameHeight); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.CropHelper; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class CropHelperTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCrop() { | ||||
|         testCrop(new Size(1600, 1600), AspectRatio.of(16, 16)); | ||||
|         testCrop(new Size(1600, 1600), AspectRatio.of(16, 9)); | ||||
|         testCrop(new Size(1600, 1600), AspectRatio.of(9, 16)); | ||||
|     } | ||||
| 
 | ||||
|     private void testCrop(final Size inSize, final AspectRatio outRatio) { | ||||
|         AspectRatio inRatio = AspectRatio.of(inSize.getWidth(), inSize.getHeight()); | ||||
|         Rect out = CropHelper.computeCrop(inSize, outRatio); | ||||
|         Size outSize = new Size(out.width(), out.height()); | ||||
|         assertTrue(outRatio.matches(outSize)); | ||||
| 
 | ||||
|         if (outRatio.matches(inSize)) { | ||||
|             // They are equal.
 | ||||
|             assertEquals(outSize.getWidth(), inSize.getWidth()); | ||||
|             assertEquals(outSize.getHeight(), inSize.getHeight()); | ||||
|         } else if (outRatio.toFloat() > inRatio.toFloat()) { | ||||
|             // Width must match.
 | ||||
|             assertEquals(outSize.getWidth(), inSize.getWidth()); | ||||
|             assertNotEquals(outSize.getHeight(), inSize.getHeight()); | ||||
|         } else { | ||||
|             // Height must match.
 | ||||
|             assertEquals(outSize.getHeight(), inSize.getHeight()); | ||||
|             assertNotEquals(outSize.getWidth(), inSize.getWidth()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,219 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.media.MediaCodecInfo; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.MediumTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertSame; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @MediumTest | ||||
| public class DeviceEncodersTest extends BaseTest { | ||||
| 
 | ||||
|     // This is guaranteed to work, see
 | ||||
|     // https://developer.android.com/guide/topics/media/media-formats
 | ||||
|     private final static Size GUARANTEED_SIZE = new Size(176, 144); | ||||
| 
 | ||||
|     private boolean enabled; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         enabled = DeviceEncoders.ENABLED; | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         DeviceEncoders.ENABLED = enabled; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private DeviceEncoders create() { | ||||
|         return new DeviceEncoders(DeviceEncoders.MODE_RESPECT_ORDER, | ||||
|                 "video/avc", | ||||
|                 "audio/mp4a-latm", | ||||
|                 0, | ||||
|                 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetDeviceEncoders() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             List<MediaCodecInfo> infos = deviceEncoders.getDeviceEncoders(); | ||||
|             for (MediaCodecInfo info : infos) { | ||||
|                 assertTrue(info.isEncoder()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testIsHardwareEncoder() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             assertFalse(deviceEncoders.isHardwareEncoder("OMX.google.encoder")); | ||||
|             assertTrue(deviceEncoders.isHardwareEncoder("OMX.other.encoder")); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFindDeviceEncoder() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders(); | ||||
|             MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, | ||||
|                     "video/avc", DeviceEncoders.MODE_RESPECT_ORDER, 0); | ||||
|             assertNotNull(encoder); | ||||
|             List<String> encoderTypes = Arrays.asList(encoder.getSupportedTypes()); | ||||
|             assertTrue(encoderTypes.contains("video/avc")); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetVideoEncoder() { | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             DeviceEncoders deviceEncoders = create(); | ||||
|             assertNotNull(deviceEncoders.getVideoEncoder()); | ||||
|         } | ||||
| 
 | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         assertNull(deviceEncoders.getVideoEncoder()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetAudioEncoder() { | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             DeviceEncoders deviceEncoders = create(); | ||||
|             assertNotNull(deviceEncoders.getAudioEncoder()); | ||||
|         } | ||||
| 
 | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         assertNull(deviceEncoders.getAudioEncoder()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         Size input = new Size(GUARANTEED_SIZE.getWidth(), GUARANTEED_SIZE.getHeight()); | ||||
|         Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|         assertSame(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_scalesDown() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             Size input = new Size( | ||||
|                     GUARANTEED_SIZE.getWidth() * 1000, | ||||
|                     GUARANTEED_SIZE.getHeight() * 1000); | ||||
|             try { | ||||
|                 Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|                 assertTrue(AspectRatio.of(input).matches(output, 0.01F)); | ||||
|             } catch (RuntimeException e) { | ||||
|                 // The scaled down size happens to be not supported.
 | ||||
|                 // I see no way of testing this easily if we're not sure of supported ranges.
 | ||||
|                 // This depends highly on the alignment since scaling down, while keeping AR,
 | ||||
|                 // can change the alignment and require width / height changes.
 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_aligns() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             Size input = new Size(GUARANTEED_SIZE.getWidth() + 1, | ||||
|                     GUARANTEED_SIZE.getHeight() + 1); | ||||
|             Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|             assertTrue(output.getWidth() <= input.getWidth()); | ||||
|             assertTrue(output.getHeight() <= input.getHeight()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoBitRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedVideoBitRate(input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoBitRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -1000; | ||||
|             int output = deviceEncoders.getSupportedVideoBitRate(input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedAudioBitRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedAudioBitRate(input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedAudioBitRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -1000; | ||||
|             int output = deviceEncoders.getSupportedAudioBitRate(input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedFrameRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedVideoFrameRate(GUARANTEED_SIZE, input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedFrameRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -10; | ||||
|             Size inputSize = deviceEncoders.getSupportedVideoSize(GUARANTEED_SIZE); | ||||
|             int output = deviceEncoders.getSupportedVideoFrameRate(inputSize, input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,109 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.OrientationEventListener; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.OrientationHelper; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| import static org.mockito.Mockito.*; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class OrientationHelperTest extends BaseTest { | ||||
| 
 | ||||
|     private OrientationHelper helper; | ||||
|     private OrientationHelper.Callback callback; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 callback = mock(OrientationHelper.Callback.class); | ||||
|                 helper = new OrientationHelper(getContext(), callback); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         callback = null; | ||||
|         helper = null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEnable() { | ||||
|         // On some API levels, enable() needs to be run on the UI thread.
 | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 assertEquals(helper.getLastDisplayOffset(), -1); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), -1); | ||||
| 
 | ||||
|                 helper.enable(); | ||||
|                 assertNotEquals(helper.getLastDisplayOffset(), -1); // Don't know about device orientation.
 | ||||
| 
 | ||||
|                 // Ensure nothing bad if called twice.
 | ||||
|                 helper.enable(); | ||||
|                 assertNotEquals(helper.getLastDisplayOffset(), -1); | ||||
| 
 | ||||
|                 helper.disable(); | ||||
|                 assertEquals(helper.getLastDisplayOffset(), -1); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), -1); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRotation() { | ||||
|         // On some API levels, enable() needs to be run on the UI thread.
 | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 // Sometimes (on some APIs) the helper will trigger an update to 0
 | ||||
|                 // right after enabling. But that's fine for us, times(1) will be OK either way.
 | ||||
|                 helper.enable(); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 0); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(10); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 0); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(-10); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 0); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(44); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 0); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(360); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 0); | ||||
| 
 | ||||
|                 // Callback called just once.
 | ||||
|                 verify(callback, times(1)).onDeviceOrientationChanged(0); | ||||
| 
 | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(90); | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(91); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 90); | ||||
|                 verify(callback, times(1)).onDeviceOrientationChanged(90); | ||||
| 
 | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(180); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 180); | ||||
|                 verify(callback, times(1)).onDeviceOrientationChanged(180); | ||||
| 
 | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(270); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 270); | ||||
|                 verify(callback, times(1)).onDeviceOrientationChanged(270); | ||||
| 
 | ||||
|                 // It is still 270 after ORIENTATION_UNKNOWN
 | ||||
|                 helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); | ||||
|                 assertEquals(helper.getLastDeviceOrientation(), 270); | ||||
|                 verify(callback, times(1)).onDeviceOrientationChanged(270); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.ImageFormat; | ||||
| import android.graphics.YuvImage; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.RotationHelper; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class RotationHelperTest extends BaseTest { | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void testInvalidRotation1() { | ||||
|         RotationHelper.rotate(new byte[10], new Size(1, 1), -1); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void testInvalidRotation2() { | ||||
|         RotationHelper.rotate(new byte[10], new Size(1, 1), -90); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void testInvalidRotation3() { | ||||
|         RotationHelper.rotate(new byte[10], new Size(1, 1), 360); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRotate() { | ||||
|         // Just test that nothing happens.
 | ||||
|         Size inputSize = new Size(160, 90); | ||||
|         int inputSizeBits = inputSize.getWidth() * inputSize.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.NV21); | ||||
|         int inputSizeBytes = (int) Math.ceil(inputSizeBits / 8.0d); | ||||
|         byte[] input = new byte[inputSizeBytes]; | ||||
|         byte[] output = RotationHelper.rotate(input, inputSize, 90); | ||||
|         assertEquals(input.length, output.length); | ||||
| 
 | ||||
|         Size outputSize = inputSize.flip(); | ||||
|         YuvImage image = new YuvImage(output, ImageFormat.NV21, outputSize.getWidth(), outputSize.getHeight(), null); | ||||
|         assertNotNull(image); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,242 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.tasks.Tasks; | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.WorkerHandler; | ||||
| import com.otaliastudios.cameraview.tools.Op; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.concurrent.Callable; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class WorkerHandlerTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetFromCache() { | ||||
|         WorkerHandler first = WorkerHandler.get("first"); | ||||
|         WorkerHandler second = WorkerHandler.get("first"); | ||||
|         assertSame(first, second); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetAnother() { | ||||
|         WorkerHandler first = WorkerHandler.get("first"); | ||||
|         WorkerHandler second = WorkerHandler.get("second"); | ||||
|         assertNotSame(first, second); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Runnable getRunnableForOp(final @NonNull Op<Boolean> op) { | ||||
|         return new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 op.controller().end(true); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Callable<Boolean> getCallableForOp(final @NonNull Op<Boolean> op) { | ||||
|         return new Callable<Boolean>() { | ||||
|             @Override | ||||
|             public Boolean call() { | ||||
|                 op.controller().end(true); | ||||
|                 return true; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Callable<Void> getThrowCallable() { | ||||
|         return new Callable<Void>() { | ||||
|             @Override | ||||
|             public Void call() { | ||||
|                 throw new RuntimeException("Fake error"); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private void waitOp(@NonNull Op<Boolean> op) { | ||||
|         Boolean result = op.await(500); | ||||
|         assertNotNull(result); | ||||
|         assertTrue(result); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFallbackExecute() { | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         WorkerHandler.execute(getRunnableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPostRunnable() { | ||||
|         WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.post(getRunnableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPostCallable() { | ||||
|         WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.post(getCallableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPostCallable_throws() { | ||||
|         WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         Task<Void> task = handler.post(getThrowCallable()); | ||||
|         try { Tasks.await(task); } catch (ExecutionException | InterruptedException ignore) {} | ||||
|         assertTrue(task.isComplete()); | ||||
|         assertFalse(task.isSuccessful()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRunRunnable_background() { | ||||
|         WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.run(getRunnableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRunRunnable_sameThread() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op1 = new Op<>(); | ||||
|         final Op<Boolean> op2 = new Op<>(); | ||||
|         handler.post(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 handler.run(getRunnableForOp(op2)); | ||||
|                 assertTrue(op2.await(0)); // Do not wait.
 | ||||
|                 op1.controller().end(true); | ||||
|             } | ||||
|         }); | ||||
|         waitOp(op1); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRunCallable_background() { | ||||
|         WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.run(getCallableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRunCallable_sameThread() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op1 = new Op<>(); | ||||
|         final Op<Boolean> op2 = new Op<>(); | ||||
|         handler.post(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 handler.run(getCallableForOp(op2)); | ||||
|                 assertTrue(op2.await(0)); // Do not wait.
 | ||||
|                 op1.controller().end(true); | ||||
|             } | ||||
|         }); | ||||
|         waitOp(op1); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRunCallable_sameThread_throws() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.post(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Task<Void> task = handler.run(getThrowCallable()); | ||||
|                 assertTrue(task.isComplete()); // Already complete
 | ||||
|                 assertFalse(task.isSuccessful()); | ||||
|                 op.controller().end(true); | ||||
|             } | ||||
|         }); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPostDelayed_tooEarly() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.post(1000, getRunnableForOp(op)); | ||||
|         assertNull(op.await(500)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPostDelayed() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         handler.post(1000, getRunnableForOp(op)); | ||||
|         assertNotNull(op.await(2000)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRemove() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         Runnable runnable = getRunnableForOp(op); | ||||
|         handler.post(1000, runnable); | ||||
|         handler.remove(runnable); | ||||
|         assertNull(op.await(2000)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetters() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         assertNotNull(handler.getExecutor()); | ||||
|         assertNotNull(handler.getHandler()); | ||||
|         assertNotNull(handler.getLooper()); | ||||
|         assertNotNull(handler.getThread()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testExecutor() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         Executor executor = handler.getExecutor(); | ||||
|         final Op<Boolean> op = new Op<>(); | ||||
|         executor.execute(getRunnableForOp(op)); | ||||
|         waitOp(op); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDestroy() { | ||||
|         final WorkerHandler handler = WorkerHandler.get("handler"); | ||||
|         assertTrue(handler.getThread().isAlive()); | ||||
|         handler.destroy(); | ||||
|         WorkerHandler newHandler = WorkerHandler.get("handler"); | ||||
|         assertNotSame(handler, newHandler); | ||||
|         assertTrue(newHandler.getThread().isAlive()); | ||||
|         // Ensure old thread dies at some point.
 | ||||
|         try { handler.getThread().join(500); } catch (InterruptedException ignore) {} | ||||
|         assertFalse(handler.getThread().isAlive()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDestroyAll() { | ||||
|         final WorkerHandler handler1 = WorkerHandler.get("handler1"); | ||||
|         final WorkerHandler handler2 = WorkerHandler.get("handler2"); | ||||
|         WorkerHandler.destroyAll(); | ||||
|         WorkerHandler newHandler1 = WorkerHandler.get("handler1"); | ||||
|         WorkerHandler newHandler2 = WorkerHandler.get("handler2"); | ||||
|         assertNotSame(handler1, newHandler1); | ||||
|         assertNotSame(handler2, newHandler2); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,104 @@ | ||||
| package com.otaliastudios.cameraview.markers; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.PointF; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.FrameLayout; | ||||
| 
 | ||||
| import androidx.test.annotation.UiThreadTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Before; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.mockito.Mockito.atLeastOnce; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| public class DefaultAutoFocusMarkerTest extends BaseTest { | ||||
| 
 | ||||
|     private DefaultAutoFocusMarker marker; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         marker = new DefaultAutoFocusMarker(); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         marker = null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnAttach() { | ||||
|         assertNull(marker.mContainer); | ||||
|         assertNull(marker.mFill); | ||||
|         ViewGroup container = new FrameLayout(getContext()); | ||||
|         View result = marker.onAttach(getContext(), container); | ||||
|         assertNotNull(result); | ||||
|         assertNotNull(marker.mContainer); | ||||
|         assertNotNull(marker.mFill); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testOnAutoFocusStart() { | ||||
|         View mockContainer = spy(new View(getContext())); | ||||
|         View mockFill = spy(new View(getContext())); | ||||
|         marker.mContainer = mockContainer; | ||||
|         marker.mFill = mockFill; | ||||
|         marker.onAutoFocusStart(AutoFocusTrigger.GESTURE, new PointF()); | ||||
|         verify(mockContainer, atLeastOnce()).clearAnimation(); | ||||
|         verify(mockFill, atLeastOnce()).clearAnimation(); | ||||
|         verify(mockContainer, atLeastOnce()).animate(); | ||||
|         verify(mockFill, atLeastOnce()).animate(); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testOnAutoFocusStart_fromMethod() { | ||||
|         View mockContainer = spy(new View(getContext())); | ||||
|         View mockFill = spy(new View(getContext())); | ||||
|         marker.mContainer = mockContainer; | ||||
|         marker.mFill = mockFill; | ||||
|         marker.onAutoFocusStart(AutoFocusTrigger.METHOD, new PointF()); | ||||
|         verify(mockContainer, never()).clearAnimation(); | ||||
|         verify(mockFill, never()).clearAnimation(); | ||||
|         verify(mockContainer, never()).animate(); | ||||
|         verify(mockFill, never()).animate(); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testOnAutoFocusEnd() { | ||||
|         View mockContainer = spy(new View(getContext())); | ||||
|         View mockFill = spy(new View(getContext())); | ||||
|         marker.mContainer = mockContainer; | ||||
|         marker.mFill = mockFill; | ||||
|         marker.onAutoFocusEnd(AutoFocusTrigger.GESTURE, true, new PointF()); | ||||
|         verify(mockContainer, atLeastOnce()).animate(); | ||||
|         verify(mockFill, atLeastOnce()).animate(); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testOnAutoFocusEnd_fromMethod() { | ||||
|         View mockContainer = spy(new View(getContext())); | ||||
|         View mockFill = spy(new View(getContext())); | ||||
|         marker.mContainer = mockContainer; | ||||
|         marker.mFill = mockFill; | ||||
|         marker.onAutoFocusEnd(AutoFocusTrigger.METHOD, true, new PointF()); | ||||
|         verify(mockContainer, never()).animate(); | ||||
|         verify(mockFill, never()).animate(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,128 @@ | ||||
| package com.otaliastudios.cameraview.markers; | ||||
| 
 | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.graphics.PointF; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.TestActivity; | ||||
| import com.otaliastudios.cameraview.tools.SdkExclude; | ||||
| 
 | ||||
| import org.junit.Assert; | ||||
| import org.junit.Before; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.rule.ActivityTestRule; | ||||
| 
 | ||||
| import static org.mockito.ArgumentMatchers.anyFloat; | ||||
| import static org.mockito.Mockito.reset; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| /** | ||||
|  * Not clear why, but for some reason on API 28+ the UiThreadTests here crash for an internal NPE | ||||
|  * in FrameLayout.onMeasure. | ||||
|  */ | ||||
| @SdkExclude(minSdkVersion = 28, maxSdkVersion = 29) | ||||
| @TargetApi(17) | ||||
| public class MarkerLayoutTest extends BaseTest { | ||||
| 
 | ||||
| 
 | ||||
|     @Rule | ||||
|     public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class); | ||||
| 
 | ||||
|     private MarkerLayout markerLayout; | ||||
|     private AutoFocusMarker autoFocusMarker; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 TestActivity a = rule.getActivity(); | ||||
|                 markerLayout = spy(new MarkerLayout(a)); | ||||
|                 a.inflate(markerLayout); | ||||
|                 autoFocusMarker = spy(new DefaultAutoFocusMarker()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnMarker_callsOnAttach() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, autoFocusMarker); | ||||
|                 Mockito.verify(autoFocusMarker, times(1)).onAttach( | ||||
|                         Mockito.any(Context.class), | ||||
|                         Mockito.eq(markerLayout)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnMarker_addsView() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Assert.assertEquals(markerLayout.getChildCount(), 0); | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, autoFocusMarker); | ||||
|                 Assert.assertEquals(markerLayout.getChildCount(), 1); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnMarker_removesView() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, autoFocusMarker); | ||||
|                 Assert.assertEquals(markerLayout.getChildCount(), 1); | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, autoFocusMarker); | ||||
|                 Assert.assertEquals(markerLayout.getChildCount(), 1); | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, null); | ||||
|                 Assert.assertEquals(markerLayout.getChildCount(), 0); | ||||
| 
 | ||||
|                 Mockito.verify(autoFocusMarker, times(2)).onAttach( | ||||
|                         Mockito.any(Context.class), | ||||
|                         Mockito.eq(markerLayout)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testOnEvent() { | ||||
|         uiSync(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 final View mockView = spy(new View(getContext())); | ||||
|                 // These fail, however it's not really needed.
 | ||||
|                 // when(mockView.getWidth()).thenReturn(50);
 | ||||
|                 // when(mockView.getHeight()).thenReturn(50);
 | ||||
|                 AutoFocusMarker mockMarker = new AutoFocusMarker() { | ||||
|                     public void onAutoFocusStart(@NonNull AutoFocusTrigger trigger, @NonNull PointF point) { } | ||||
|                     public void onAutoFocusEnd(@NonNull AutoFocusTrigger trigger, boolean successful, @NonNull PointF point) { } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public View onAttach(@NonNull Context context, @NonNull ViewGroup container) { | ||||
|                         return mockView; | ||||
|                     } | ||||
|                 }; | ||||
|                 markerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, mockMarker); | ||||
|                 reset(mockView); | ||||
|                 markerLayout.onEvent(MarkerLayout.TYPE_AUTOFOCUS, new PointF[]{new PointF(0, 0)}); | ||||
|                 verify(mockView, times(1)).clearAnimation(); | ||||
|                 verify(mockView, times(1)).setTranslationX(anyFloat()); | ||||
|                 verify(mockView, times(1)).setTranslationY(anyFloat()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| package com.otaliastudios.cameraview.markers; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| import android.graphics.PointF; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.StyleableRes; | ||||
| import androidx.arch.core.util.Function; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.R; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| import com.otaliastudios.cameraview.size.SizeSelectorParser; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static junit.framework.TestCase.assertNotNull; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.anyBoolean; | ||||
| import static org.mockito.ArgumentMatchers.anyInt; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class MarkerParserTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testNullConstructor() { | ||||
|         TypedArray array = mock(TypedArray.class); | ||||
|         when(array.hasValue(R.styleable.CameraView_cameraAutoFocusMarker)).thenReturn(false); | ||||
|         when(array.getString(R.styleable.CameraView_cameraAutoFocusMarker)).thenReturn(null); | ||||
|         MarkerParser parser = new MarkerParser(array); | ||||
|         assertNull(parser.getAutoFocusMarker()); | ||||
|     } | ||||
|     @Test | ||||
|     public void testConstructor() { | ||||
|         TypedArray array = mock(TypedArray.class); | ||||
|         when(array.hasValue(R.styleable.CameraView_cameraAutoFocusMarker)).thenReturn(true); | ||||
|         when(array.getString(R.styleable.CameraView_cameraAutoFocusMarker)).thenReturn(Marker.class.getName()); | ||||
|         MarkerParser parser = new MarkerParser(array); | ||||
|         assertNotNull(parser.getAutoFocusMarker()); | ||||
|         assertTrue(parser.getAutoFocusMarker() instanceof Marker); | ||||
|     } | ||||
| 
 | ||||
|     public static class Marker implements AutoFocusMarker { | ||||
| 
 | ||||
|         public Marker() { } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public View onAttach(@NonNull Context context, @NonNull ViewGroup container) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onAutoFocusStart(@NonNull AutoFocusTrigger trigger, @NonNull PointF point) { } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onAutoFocusEnd(@NonNull AutoFocusTrigger trigger, boolean successful, @NonNull PointF point) { } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,137 @@ | ||||
| package com.otaliastudios.cameraview.metering; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| import android.graphics.PointF; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.RectF; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.R; | ||||
| import com.otaliastudios.cameraview.markers.AutoFocusMarker; | ||||
| import com.otaliastudios.cameraview.markers.AutoFocusTrigger; | ||||
| import com.otaliastudios.cameraview.markers.MarkerParser; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.invocation.InvocationOnMock; | ||||
| import org.mockito.stubbing.Answer; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static junit.framework.TestCase.assertNotNull; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.atLeastOnce; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class MeteringRegionsTest extends BaseTest { | ||||
| 
 | ||||
|     private final Size bounds = new Size(1000, 1000); | ||||
| 
 | ||||
|     private void checkRegion(@NonNull MeteringRegion region, @NonNull PointF center, int weight) { | ||||
|         assertEquals(center.x, region.mRegion.centerX(), 0.01F); | ||||
|         assertEquals(center.y, region.mRegion.centerY(), 0.01F); | ||||
|         assertEquals(weight, region.mWeight); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFromPoint() { | ||||
|         PointF center = new PointF(500, 500); | ||||
|         MeteringRegions regions = MeteringRegions.fromPoint(bounds, center); | ||||
|         assertEquals(2, regions.mRegions.size()); | ||||
|         MeteringRegion first = regions.mRegions.get(0); | ||||
|         MeteringRegion second = regions.mRegions.get(1); | ||||
|         checkRegion(first, center, MeteringRegion.MAX_WEIGHT); | ||||
|         checkRegion(second, center, | ||||
|                 Math.round(MeteringRegions.BLUR_FACTOR_WEIGHT * MeteringRegion.MAX_WEIGHT)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFromArea() { | ||||
|         RectF area = new RectF(400, 400, 600, 600); | ||||
|         MeteringRegions regions = MeteringRegions.fromArea(bounds, area); | ||||
|         assertEquals(1, regions.mRegions.size()); | ||||
|         MeteringRegion region = regions.mRegions.get(0); | ||||
|         checkRegion(region, new PointF(area.centerX(), area.centerY()), MeteringRegion.MAX_WEIGHT); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFromArea_withBlur() { | ||||
|         RectF area = new RectF(400, 400, 600, 600); | ||||
|         MeteringRegions regions = MeteringRegions.fromArea(bounds, area, | ||||
|                 MeteringRegion.MAX_WEIGHT, true); | ||||
|         assertEquals(2, regions.mRegions.size()); | ||||
|         MeteringRegion first = regions.mRegions.get(0); | ||||
|         MeteringRegion second = regions.mRegions.get(1); | ||||
|         PointF center = new PointF(area.centerX(), area.centerY()); | ||||
|         checkRegion(first, center, MeteringRegion.MAX_WEIGHT); | ||||
|         checkRegion(second, center, | ||||
|                 Math.round(MeteringRegions.BLUR_FACTOR_WEIGHT * MeteringRegion.MAX_WEIGHT)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testTransform() { | ||||
|         MeteringTransform transform = mock(MeteringTransform.class); | ||||
|         when(transform.transformMeteringPoint(any(PointF.class))).then(new Answer<PointF>() { | ||||
|             @Override | ||||
|             public PointF answer(InvocationOnMock invocation) { | ||||
|                 PointF in = invocation.getArgument(0); | ||||
|                 // This will swap x and y coordinates
 | ||||
|                 //noinspection SuspiciousNameCombination
 | ||||
|                 return new PointF(in.y, in.x); | ||||
|             } | ||||
|         }); | ||||
|         RectF area = new RectF(0, 0, 100, 500); // tall area
 | ||||
|         RectF expected = new RectF(0, 0, 500, 100); // wide area
 | ||||
|         MeteringRegions regions = MeteringRegions.fromArea(bounds, area); | ||||
|         MeteringRegions transformed = regions.transform(transform); | ||||
|         verify(transform, times(4)).transformMeteringPoint(any(PointF.class)); | ||||
|         assertEquals(1, transformed.mRegions.size()); | ||||
|         assertEquals(expected, transformed.mRegions.get(0).mRegion); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGet() { | ||||
|         MeteringTransform<Integer> transform = new MeteringTransform<Integer>() { | ||||
|             @NonNull | ||||
|             @Override | ||||
|             public PointF transformMeteringPoint(@NonNull PointF point) { | ||||
|                 return point; | ||||
|             } | ||||
| 
 | ||||
|             @NonNull | ||||
|             @Override | ||||
|             public Integer transformMeteringRegion(@NonNull RectF region, int weight) { | ||||
|                 return weight; | ||||
|             } | ||||
|         }; | ||||
|         MeteringRegions regions = MeteringRegions.fromArea(bounds, | ||||
|                 new RectF(400, 400, 600, 600), | ||||
|                 900, | ||||
|                 true); | ||||
|         assertEquals(2, regions.mRegions.size()); | ||||
|         List<Integer> result = regions.get(1, transform); | ||||
|         assertEquals(1, result.size()); | ||||
|         assertEquals(900, (int) result.get(0)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,78 @@ | ||||
| package com.otaliastudios.cameraview.overlay; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseEglTest; | ||||
| import com.otaliastudios.cameraview.internal.GlTextureDrawer; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.hamcrest.BaseMatcher; | ||||
| import org.hamcrest.Description; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertThat; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class OverlayDrawerTest extends BaseEglTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDraw() { | ||||
|         Overlay overlay = mock(Overlay.class); | ||||
|         OverlayDrawer drawer = new OverlayDrawer(overlay, new Size(WIDTH, HEIGHT)); | ||||
|         drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); | ||||
|         verify(overlay, times(1)).drawOn( | ||||
|                 eq(Overlay.Target.PICTURE_SNAPSHOT), | ||||
|                 any(Canvas.class)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetTransform() { | ||||
|         // We'll check that the transform is not all zeros, which is highly unlikely
 | ||||
|         // (the default transform should be the identity matrix)
 | ||||
|         OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); | ||||
|         drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); | ||||
|         assertThat(drawer.getTransform(), new BaseMatcher<float[]>() { | ||||
|             public void describeTo(Description description) { } | ||||
|             public boolean matches(Object item) { | ||||
|                 float[] array = (float[]) item; | ||||
|                 for (float value : array) { | ||||
|                     if (value != 0.0F) return true; | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRender() { | ||||
|         OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); | ||||
|         drawer.mTextureDrawer = spy(drawer.mTextureDrawer); | ||||
| 
 | ||||
|         drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); | ||||
|         drawer.render(0L); | ||||
|         verify(drawer.mTextureDrawer, times(1)).draw(0L); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRelease() { | ||||
|         OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); | ||||
|         GlTextureDrawer textureDrawer = spy(drawer.mTextureDrawer); | ||||
|         drawer.mTextureDrawer = textureDrawer; | ||||
|         drawer.release(); | ||||
|         verify(textureDrawer, times(1)).release(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,174 @@ | ||||
| package com.otaliastudios.cameraview.overlay; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.res.XmlResourceParser; | ||||
| import android.graphics.Canvas; | ||||
| import android.util.AttributeSet; | ||||
| import android.util.Xml; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.annotation.UiThreadTest; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.anyFloat; | ||||
| import static org.mockito.ArgumentMatchers.anyLong; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.reset; | ||||
| import static org.mockito.Mockito.spy; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class OverlayLayoutTest extends BaseTest { | ||||
| 
 | ||||
|     private OverlayLayout overlayLayout; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         overlayLayout = spy(new OverlayLayout(getContext())); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         overlayLayout = null; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testIsOverlay_LayoutParams() { | ||||
|         ViewGroup.LayoutParams params; | ||||
| 
 | ||||
|         params = new ViewGroup.LayoutParams(10, 10); | ||||
|         assertFalse(overlayLayout.isOverlay(params)); | ||||
| 
 | ||||
|         params = new OverlayLayout.LayoutParams(10, 10); | ||||
|         assertTrue(overlayLayout.isOverlay(params)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testIsOverlay_attributeSet() throws Exception { | ||||
|         int layout1 = com.otaliastudios.cameraview.test.R.layout.overlay; | ||||
|         int layout2 = com.otaliastudios.cameraview.test.R.layout.not_overlay; | ||||
| 
 | ||||
|         AttributeSet set1 = getAttributeSet(layout1); | ||||
|         assertTrue(overlayLayout.isOverlay(set1)); | ||||
| 
 | ||||
|         AttributeSet set2 = getAttributeSet(layout2); | ||||
|         assertFalse(overlayLayout.isOverlay(set2)); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private AttributeSet getAttributeSet(int layout) throws Exception { | ||||
|         // Get the attribute set in the correct state: use a parser and move to START_TAG
 | ||||
|         XmlResourceParser parser = getContext().getResources().getLayout(layout); | ||||
|         //noinspection StatementWithEmptyBody
 | ||||
|         while (parser.next() != XmlResourceParser.START_TAG) {} | ||||
|         return Xml.asAttributeSet(parser); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testLayoutParams_drawsOn() { | ||||
|         OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); | ||||
| 
 | ||||
|         assertFalse(params.drawsOn(Overlay.Target.PREVIEW)); | ||||
|         assertFalse(params.drawsOn(Overlay.Target.PICTURE_SNAPSHOT)); | ||||
|         assertFalse(params.drawsOn(Overlay.Target.VIDEO_SNAPSHOT)); | ||||
| 
 | ||||
|         params.drawOnPreview = true; | ||||
|         assertTrue(params.drawsOn(Overlay.Target.PREVIEW)); | ||||
|         params.drawOnPictureSnapshot = true; | ||||
|         assertTrue(params.drawsOn(Overlay.Target.PICTURE_SNAPSHOT)); | ||||
|         params.drawOnVideoSnapshot = true; | ||||
|         assertTrue(params.drawsOn(Overlay.Target.VIDEO_SNAPSHOT)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testLayoutParams_toString() { | ||||
|         OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); | ||||
|         String string = params.toString(); | ||||
|         assertTrue(string.contains("drawOnPreview")); | ||||
|         assertTrue(string.contains("drawOnPictureSnapshot")); | ||||
|         assertTrue(string.contains("drawOnVideoSnapshot")); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testDrawChild() { | ||||
|         Canvas canvas = new Canvas(); | ||||
|         OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); | ||||
|         View child = new View(getContext()); | ||||
|         child.setLayoutParams(params); | ||||
|         when(overlayLayout.doDrawChild(canvas, child, 0)).thenReturn(true); | ||||
| 
 | ||||
|         overlayLayout.currentTarget = Overlay.Target.PREVIEW; | ||||
|         assertFalse(overlayLayout.drawChild(canvas, child, 0)); | ||||
|         params.drawOnPreview = true; | ||||
|         assertTrue(overlayLayout.drawChild(canvas, child, 0)); | ||||
| 
 | ||||
|         overlayLayout.currentTarget = Overlay.Target.PICTURE_SNAPSHOT; | ||||
|         assertFalse(overlayLayout.drawChild(canvas, child, 0)); | ||||
|         params.drawOnPictureSnapshot = true; | ||||
|         assertTrue(overlayLayout.drawChild(canvas, child, 0)); | ||||
| 
 | ||||
|         overlayLayout.currentTarget = Overlay.Target.VIDEO_SNAPSHOT; | ||||
|         assertFalse(overlayLayout.drawChild(canvas, child, 0)); | ||||
|         params.drawOnVideoSnapshot = true; | ||||
|         assertTrue(overlayLayout.drawChild(canvas, child, 0)); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testDraw() { | ||||
|         Canvas canvas = new Canvas(); | ||||
|         when(overlayLayout.drawsOn(Overlay.Target.PREVIEW)).thenReturn(false); | ||||
|         overlayLayout.draw(canvas); | ||||
|         verify(overlayLayout, never()).drawOn(Overlay.Target.PREVIEW, canvas); | ||||
| 
 | ||||
|         when(overlayLayout.drawsOn(Overlay.Target.PREVIEW)).thenReturn(true); | ||||
|         overlayLayout.draw(canvas); | ||||
|         verify(overlayLayout, times(1)).drawOn(Overlay.Target.PREVIEW, canvas); | ||||
|     } | ||||
| 
 | ||||
|     @UiThreadTest | ||||
|     @Test | ||||
|     public void testDrawOn() { | ||||
|         Canvas canvas = spy(new Canvas()); | ||||
|         View child = new View(getContext()); | ||||
|         OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); | ||||
|         params.drawOnPreview = true; | ||||
|         params.drawOnPictureSnapshot = true; | ||||
|         params.drawOnVideoSnapshot = true; | ||||
|         overlayLayout.addView(child, params); | ||||
| 
 | ||||
|         overlayLayout.drawOn(Overlay.Target.PREVIEW, canvas); | ||||
|         verify(canvas, never()).scale(anyFloat(), anyFloat()); | ||||
|         verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); | ||||
|         reset(canvas); | ||||
|         reset(overlayLayout); | ||||
| 
 | ||||
|         overlayLayout.drawOn(Overlay.Target.PICTURE_SNAPSHOT, canvas); | ||||
|         verify(canvas, times(1)).scale(anyFloat(), anyFloat()); | ||||
|         verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); | ||||
|         reset(canvas); | ||||
|         reset(overlayLayout); | ||||
| 
 | ||||
|         overlayLayout.drawOn(Overlay.Target.VIDEO_SNAPSHOT, canvas); | ||||
|         verify(canvas, times(1)).scale(anyFloat(), anyFloat()); | ||||
|         verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); | ||||
|         reset(canvas); | ||||
|         reset(overlayLayout); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.otaliastudios.cameraview.picture; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.PictureResult; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import java.lang.reflect.Constructor; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNull; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class PictureRecorderTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRecorder() throws Exception { | ||||
|         PictureResult.Stub result = createStub(); | ||||
|         PictureRecorder.PictureResultListener listener = Mockito.mock(PictureRecorder.PictureResultListener.class); | ||||
|         PictureRecorder recorder = new PictureRecorder(result, listener) { | ||||
|             public void take() { | ||||
|                 dispatchResult(); | ||||
|             } | ||||
|         }; | ||||
|         recorder.take(); | ||||
|         Mockito.verify(listener, Mockito.times(1)).onPictureResult(result, null); | ||||
|         assertNull(recorder.mListener); | ||||
|         assertNull(recorder.mResult); | ||||
|     } | ||||
| 
 | ||||
|     private PictureResult.Stub createStub() throws Exception { | ||||
|         Constructor<PictureResult.Stub> constructor = PictureResult.Stub.class.getDeclaredConstructor(); | ||||
|         constructor.setAccessible(true); | ||||
|         return constructor.newInstance(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.filter.Filter; | ||||
| import com.otaliastudios.cameraview.filter.Filters; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class GlCameraPreviewTest extends CameraPreviewTest<GlCameraPreview> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected GlCameraPreview createPreview(Context context, ViewGroup parent) { | ||||
|         return new GlCameraPreview(context, parent); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected float getCropScaleY() { | ||||
|         return 1F / preview.mCropScaleY; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected float getCropScaleX() { | ||||
|         return 1F / preview.mCropScaleX; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSetFilter() { | ||||
|         Filter filter = Filters.BLACK_AND_WHITE.newInstance(); | ||||
|         preview.setFilter(filter); | ||||
|         assertEquals(filter, preview.getCurrentFilter()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.filter.Filter; | ||||
| import com.otaliastudios.cameraview.preview.CameraPreview; | ||||
| 
 | ||||
| public class MockCameraPreview extends CameraPreview<View, Void> implements FilterCameraPreview { | ||||
| 
 | ||||
|     public MockCameraPreview(Context context, ViewGroup parent) { | ||||
|         super(context, parent); | ||||
|     } | ||||
| 
 | ||||
|     private View rootView; | ||||
|     private Filter filter; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean supportsCropping() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected View onCreateView(@NonNull Context context, @NonNull ViewGroup parent) { | ||||
|         rootView = new View(context); | ||||
|         return rootView; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Class<Void> getOutputClass() { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Void getOutput() { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public View getRootView() { | ||||
|         return rootView; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setFilter(@NonNull Filter filter) { | ||||
|         this.filter = filter; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Filter getCurrentFilter() { | ||||
|         return filter; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class SurfaceCameraPreviewTest extends CameraPreviewTest<SurfaceCameraPreview> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected SurfaceCameraPreview createPreview(Context context, ViewGroup parent) { | ||||
|         return new SurfaceCameraPreview(context, parent); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected float getCropScaleX() { | ||||
|         return 1F; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected float getCropScaleY() { | ||||
|         return 1F; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,211 @@ | ||||
| package com.otaliastudios.cameraview.size; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.StyleableRes; | ||||
| import androidx.arch.core.util.Function; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.anyBoolean; | ||||
| import static org.mockito.ArgumentMatchers.anyInt; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class SizeSelectorParserTest extends BaseTest { | ||||
| 
 | ||||
|     private MockTypedArray input; | ||||
|     private List<Size> sizes = Arrays.asList( | ||||
|             new Size(100, 200), | ||||
|             new Size(150, 300), | ||||
|             new Size(600, 900), | ||||
|             new Size(600, 600), | ||||
|             new Size(1600, 900), | ||||
|             new Size(30, 40), | ||||
|             new Size(40, 30), | ||||
|             new Size(2000, 4000) | ||||
|     ); | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         input = new MockTypedArray(); | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         input = null; | ||||
|     } | ||||
| 
 | ||||
|     private void doAssert(@NonNull Function<List<Size>, Void> assertions) { | ||||
|         SizeSelectorParser parser = new SizeSelectorParser(input.array); | ||||
|         assertions.apply(parser.getPictureSizeSelector().select(sizes)); | ||||
|         assertions.apply(parser.getVideoSizeSelector().select(sizes)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testWidth() { | ||||
|         input.setMinWidth(1500); | ||||
|         input.setMaxWidth(1700); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(1, input.size()); | ||||
|                 assertEquals(new Size(1600, 900), input.get(0)); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testHeight() { | ||||
|         input.setMinHeight(25); | ||||
|         input.setMaxHeight(35); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(1, input.size()); | ||||
|                 assertEquals(new Size(40, 30), input.get(0)); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testArea() { | ||||
|         input.setMinArea(30 * 30); | ||||
|         input.setMaxArea(40 * 40); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(2, input.size()); | ||||
|                 assertTrue(input.contains(new Size(40, 30))); | ||||
|                 assertTrue(input.contains(new Size(30, 40))); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSmallest() { | ||||
|         input.setSmallest(true); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(sizes.size(), input.size()); | ||||
|                 Size first = input.get(0); | ||||
|                 assertEquals(30 * 40, first.getWidth() * first.getHeight()); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testBiggest() { | ||||
|         input.setBiggest(true); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(sizes.size(), input.size()); | ||||
|                 assertEquals(new Size(2000, 4000), input.get(0)); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testAspectRatio() { | ||||
|         input.setAspectRatio("16:9"); | ||||
|         doAssert(new Function<List<Size>, Void>() { | ||||
|             @Override | ||||
|             public Void apply(List<Size> input) { | ||||
|                 assertEquals(1, input.size()); | ||||
|                 assertEquals(new Size(1600, 900), input.get(0)); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("SameParameterValue") | ||||
|     private class MockTypedArray { | ||||
|         private TypedArray array = mock(TypedArray.class); | ||||
| 
 | ||||
|         private void setIntValue(@StyleableRes int index, int value) { | ||||
|             when(array.hasValue(index)).thenReturn(true); | ||||
|             when(array.getInteger(eq(index), anyInt())).thenReturn(value); | ||||
|         } | ||||
| 
 | ||||
|         private void setBooleanValue(@StyleableRes int index, boolean value) { | ||||
|             when(array.hasValue(index)).thenReturn(true); | ||||
|             when(array.getBoolean(eq(index), anyBoolean())).thenReturn(value); | ||||
|         } | ||||
| 
 | ||||
|         private void setStringValue(@StyleableRes int index, @NonNull String value) { | ||||
|             when(array.hasValue(index)).thenReturn(true); | ||||
|             when(array.getString(index)).thenReturn(value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMinWidth(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMinWidth, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMinWidth, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMaxWidth(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMaxWidth, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMaxWidth, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMinHeight(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMinHeight, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMinHeight, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMaxHeight(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMaxHeight, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMaxHeight, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMinArea(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMinArea, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMinArea, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setMaxArea(int value) { | ||||
|             setIntValue(R.styleable.CameraView_cameraPictureSizeMaxArea, value); | ||||
|             setIntValue(R.styleable.CameraView_cameraVideoSizeMaxArea, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setSmallest(boolean value) { | ||||
|             setBooleanValue(R.styleable.CameraView_cameraPictureSizeSmallest, value); | ||||
|             setBooleanValue(R.styleable.CameraView_cameraVideoSizeSmallest, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setBiggest(boolean value) { | ||||
|             setBooleanValue(R.styleable.CameraView_cameraPictureSizeBiggest, value); | ||||
|             setBooleanValue(R.styleable.CameraView_cameraVideoSizeBiggest, value); | ||||
|         } | ||||
| 
 | ||||
|         private void setAspectRatio(@NonNull String value) { | ||||
|             setStringValue(R.styleable.CameraView_cameraPictureSizeAspectRatio, value); | ||||
|             setStringValue(R.styleable.CameraView_cameraVideoSizeAspectRatio, value); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| public class Emulator { | ||||
|     public static boolean isEmulator() { | ||||
|         // From Android's RequiresDeviceFilter
 | ||||
|         return Build.HARDWARE.equals("goldfish") | ||||
|                 || Build.HARDWARE.equals("ranchu") | ||||
|                 || Build.HARDWARE.equals("gce_x86"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,149 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.google.android.gms.tasks.OnSuccessListener; | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.otaliastudios.cameraview.controls.Control; | ||||
| 
 | ||||
| import org.mockito.Mockito; | ||||
| import org.mockito.invocation.InvocationOnMock; | ||||
| import org.mockito.stubbing.Answer; | ||||
| import org.mockito.stubbing.Stubber; | ||||
| 
 | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * A naive implementation of {@link java.util.concurrent.CountDownLatch} | ||||
|  * to help in testing. | ||||
|  */ | ||||
| public class Op<T> { | ||||
| 
 | ||||
|     public class Controller { | ||||
|         private int mToBeIgnored; | ||||
| 
 | ||||
|         private Controller() { } | ||||
| 
 | ||||
|         /** Op owner method: notifies the action started. */ | ||||
|         public void start() { | ||||
|             if (!isListening()) mToBeIgnored++; | ||||
|         } | ||||
| 
 | ||||
|         /** Op owner method: notifies the action ended. */ | ||||
|         public void end(T result) { | ||||
|             if (mToBeIgnored > 0) { | ||||
|                 mToBeIgnored--; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (isListening()) { // Should be always true.
 | ||||
|                 mResult = result; | ||||
|                 mLatch.countDown(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void from(@NonNull Task<T> task) { | ||||
|             start(); | ||||
|             task.addOnSuccessListener(new OnSuccessListener<T>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(T result) { | ||||
|                     end(result); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public Stubber from(final int invocationArgument) { | ||||
|             return Mockito.doAnswer(new Answer() { | ||||
|                 @Override | ||||
|                 public Object answer(InvocationOnMock invocation) { | ||||
|                     //noinspection unchecked
 | ||||
|                     T o = (T) invocation.getArguments()[invocationArgument]; | ||||
|                     start(); | ||||
|                     end(o); | ||||
|                     return null; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private CountDownLatch mLatch; | ||||
|     private Controller mController = new Controller(); | ||||
|     private T mResult; | ||||
| 
 | ||||
|     /** | ||||
|      * Listeners should: | ||||
|      * - call {@link #listen()} to notify they are interested in the next action | ||||
|      * - call {@link #await()} to know when the action is performed. | ||||
|      * | ||||
|      * Op owners should: | ||||
|      * - call {@link Controller#start()} when task started | ||||
|      * - call {@link Controller#end(Object)} when task ends | ||||
|      */ | ||||
|     public Op() { | ||||
|         this(true); | ||||
|     } | ||||
| 
 | ||||
|     public Op(boolean startListening) { | ||||
|         if (startListening) listen(); | ||||
|     } | ||||
| 
 | ||||
|     public Op(@NonNull Task<T> task) { | ||||
|         listen(); | ||||
|         controller().from(task); | ||||
|     } | ||||
| 
 | ||||
|     private boolean isListening() { | ||||
|         return mLatch != null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: notifies we are interested in the next action. | ||||
|      */ | ||||
|     public void listen() { | ||||
|         if (isListening()) throw new RuntimeException("Should not happen."); | ||||
|         mResult = null; | ||||
|         mLatch = new CountDownLatch(1); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits for next task action to end. | ||||
|      * @param millis milliseconds | ||||
|      * @return the action result | ||||
|      */ | ||||
|     public T await(long millis) { | ||||
|         return await(millis, TimeUnit.MILLISECONDS); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits 1 minute for next task action to end. | ||||
|      * @return the action result | ||||
|      */ | ||||
|     public T await() { | ||||
|         return await(1, TimeUnit.MINUTES); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits for next task action to end. | ||||
|      * @param time time | ||||
|      * @param unit the time unit | ||||
|      * @return the action result | ||||
|      */ | ||||
|     private T await(long time, @NonNull TimeUnit unit) { | ||||
|         try { | ||||
|             mLatch.await(time, unit); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         T result = mResult; | ||||
|         mResult = null; | ||||
|         mLatch = null; | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public Controller controller() { | ||||
|         return mController; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| 
 | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface Retry { | ||||
|     boolean emulatorOnly() default false; | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| 
 | ||||
| import org.junit.rules.TestRule; | ||||
| import org.junit.runner.Description; | ||||
| import org.junit.runners.model.Statement; | ||||
| 
 | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| public class RetryRule implements TestRule { | ||||
| 
 | ||||
|     private final static String TAG = RetryRule.class.getSimpleName(); | ||||
|     private final static CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     private AtomicInteger retries; | ||||
| 
 | ||||
|     public RetryRule(int retries) { | ||||
|         this.retries = new AtomicInteger(retries); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Statement apply(final Statement base, final Description description) { | ||||
|         return new Statement() { | ||||
|             @Override | ||||
|             public void evaluate() throws Throwable { | ||||
|                 Retry retry = description.getAnnotation(Retry.class); | ||||
|                 if (retry == null || retry.emulatorOnly() && !Emulator.isEmulator()) { | ||||
|                     base.evaluate(); | ||||
|                 } else { | ||||
|                     Throwable caught = null; | ||||
|                     while (retries.getAndDecrement() > 0) { | ||||
|                         try { | ||||
|                             base.evaluate(); | ||||
|                             return; | ||||
|                         } catch (Throwable throwable) { | ||||
|                             LOG.e("[RETRY] Test failed.", retries.get(), | ||||
|                                     "retries available..."); | ||||
|                             LOG.e("*******************************************************"); | ||||
|                             caught = throwable; | ||||
|                         } | ||||
|                     } | ||||
|                     if (caught != null) { | ||||
|                         throw caught; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| /** | ||||
|  * Like {@link androidx.test.filters.SdkSuppress}, but negative. | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target({ElementType.TYPE, ElementType.METHOD}) | ||||
| public @interface SdkExclude { | ||||
|     /** The minimum API level to drop (inclusive) */ | ||||
|     int minSdkVersion() default 1; | ||||
|     /** The maximum API level to drop (inclusive) */ | ||||
|     int maxSdkVersion() default Integer.MAX_VALUE; | ||||
|     /** Whether this filter only applies to emulators */ | ||||
|     boolean emulatorOnly() default false; | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.test.internal.runner.filters.ParentFilter; | ||||
| 
 | ||||
| import org.junit.runner.Description; | ||||
| 
 | ||||
| /** | ||||
|  * Filter for {@link SdkExclude}, based on | ||||
|  * {@link androidx.test.internal.runner.TestRequestBuilder}'s SdkSuppressFilter. | ||||
|  */ | ||||
| public class SdkExcludeFilter extends ParentFilter { | ||||
| 
 | ||||
|     protected boolean evaluateTest(Description description) { | ||||
|         SdkExclude annotation = getAnnotationForTest(description); | ||||
|         if (annotation != null) { | ||||
|             if ((!annotation.emulatorOnly() || Emulator.isEmulator()) | ||||
|                     && Build.VERSION.SDK_INT >= annotation.minSdkVersion() | ||||
|                     && Build.VERSION.SDK_INT <= annotation.maxSdkVersion()) { | ||||
|                 return false; // exclude the test
 | ||||
|             } | ||||
|             return true; // run the test
 | ||||
|         } | ||||
|         return true; // no annotation, run the test
 | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private SdkExclude getAnnotationForTest(Description description) { | ||||
|         final SdkExclude s = description.getAnnotation(SdkExclude.class); | ||||
|         if (s != null) { | ||||
|             return s; | ||||
|         } | ||||
|         final Class<?> testClass = description.getTestClass(); | ||||
|         if (testClass != null) { | ||||
|             return testClass.getAnnotation(SdkExclude.class); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String describe() { | ||||
|         return "Skip tests annotated with SdkExclude"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| /** | ||||
|  * Like {@link androidx.test.filters.SdkSuppress}, but with emulatorOnly(). | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target({ElementType.TYPE, ElementType.METHOD}) | ||||
| public @interface SdkInclude { | ||||
|     /** The minimum API level to run (inclusive) */ | ||||
|     int minSdkVersion() default 1; | ||||
|     /** The maximum API level to run (inclusive) */ | ||||
|     int maxSdkVersion() default Integer.MAX_VALUE; | ||||
|     /** Whether this filter only applies to emulators */ | ||||
|     boolean emulatorOnly() default false; | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| package com.otaliastudios.cameraview.tools; | ||||
| 
 | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.test.internal.runner.filters.ParentFilter; | ||||
| 
 | ||||
| import org.junit.runner.Description; | ||||
| 
 | ||||
| /** | ||||
|  * Filter for {@link SdkInclude}, based on | ||||
|  * {@link androidx.test.internal.runner.TestRequestBuilder}'s SdkSuppressFilter. | ||||
|  */ | ||||
| public class SdkIncludeFilter extends ParentFilter { | ||||
| 
 | ||||
|     protected boolean evaluateTest(Description description) { | ||||
|         SdkInclude annotation = getAnnotationForTest(description); | ||||
|         if (annotation != null) { | ||||
|             if ((!annotation.emulatorOnly() || Emulator.isEmulator()) | ||||
|                     && Build.VERSION.SDK_INT >= annotation.minSdkVersion() | ||||
|                     && Build.VERSION.SDK_INT <= annotation.maxSdkVersion()) { | ||||
|                 return true; // run the test
 | ||||
|             } | ||||
|             return false; // drop the test
 | ||||
|         } | ||||
|         return true; // no annotation, run the test
 | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private SdkInclude getAnnotationForTest(Description description) { | ||||
|         final SdkInclude s = description.getAnnotation(SdkInclude.class); | ||||
|         if (s != null) { | ||||
|             return s; | ||||
|         } | ||||
|         final Class<?> testClass = description.getTestClass(); | ||||
|         if (testClass != null) { | ||||
|             return testClass.getAnnotation(SdkInclude.class); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String describe() { | ||||
|         return "Skip tests annotated with SdkInclude"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.otaliastudios.cameraview.video; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.VideoResult; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mockito; | ||||
| 
 | ||||
| import java.lang.reflect.Constructor; | ||||
| 
 | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class VideoRecorderTest extends BaseTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRecorder() throws Exception { | ||||
|         VideoResult.Stub result = createStub(); | ||||
|         VideoRecorder.VideoResultListener listener = Mockito.mock(VideoRecorder.VideoResultListener.class); | ||||
|         VideoRecorder recorder = new VideoRecorder(listener) { | ||||
|             @Override | ||||
|             protected void onStart() { | ||||
|                 dispatchVideoRecordingStart(); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             protected void onStop(boolean isCameraShutdown) { | ||||
|                 dispatchVideoRecordingEnd(); | ||||
|                 dispatchResult(); | ||||
|             } | ||||
|         }; | ||||
|         recorder.start(result); | ||||
|         Mockito.verify(listener,Mockito.times(1) ) | ||||
|                 .onVideoRecordingStart(); | ||||
|         recorder.stop(false); | ||||
|         Mockito.verify(listener, Mockito.times(1)) | ||||
|                 .onVideoRecordingEnd(); | ||||
|         Mockito.verify(listener, Mockito.times(1)) | ||||
|                 .onVideoResult(result, null); | ||||
|     } | ||||
| 
 | ||||
|     private VideoResult.Stub createStub() throws Exception { | ||||
|         Constructor<VideoResult.Stub> constructor = VideoResult.Stub.class.getDeclaredConstructor(); | ||||
|         constructor.setAccessible(true); | ||||
|         return constructor.newInstance(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"/> | ||||
| @ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     app:layout_drawOnPreview="true" | ||||
|     app:layout_drawOnPictureSnapshot="true" | ||||
|     app:layout_drawOnVideoSnapshot="true" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"/> | ||||
| @ -0,0 +1,21 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.UiThread; | ||||
| 
 | ||||
| /** | ||||
|  * Receives callbacks about a bitmap decoding operation. | ||||
|  */ | ||||
| public interface BitmapCallback { | ||||
| 
 | ||||
|     /** | ||||
|      * Notifies that the bitmap was successfully decoded. | ||||
|      * This is run on the UI thread. | ||||
|      * Returns a null object if a {@link OutOfMemoryError} was encountered. | ||||
|      * | ||||
|      * @param bitmap decoded bitmap, or null | ||||
|      */ | ||||
|     @UiThread | ||||
|     void onBitmapReady(@Nullable Bitmap bitmap); | ||||
| } | ||||
| @ -1,924 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.graphics.ImageFormat; | ||||
| import android.graphics.PointF; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.SurfaceTexture; | ||||
| import android.graphics.YuvImage; | ||||
| import android.hardware.Camera; | ||||
| import android.location.Location; | ||||
| import android.media.CamcorderProfile; | ||||
| import android.media.MediaRecorder; | ||||
| import android.os.Build; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.WorkerThread; | ||||
| import android.util.Log; | ||||
| import android.view.SurfaceHolder; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| @SuppressWarnings("deprecation") | ||||
| class Camera1 extends CameraController implements Camera.PreviewCallback, Camera.ErrorCallback { | ||||
| 
 | ||||
|     private static final String TAG = Camera1.class.getSimpleName(); | ||||
|     private static final CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     private Camera mCamera; | ||||
|     private boolean mIsBound = false; | ||||
| 
 | ||||
|     private final int mPostFocusResetDelay = 3000; | ||||
|     private Runnable mPostFocusResetRunnable = new Runnable() { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             if (!isCameraAvailable()) return; | ||||
|             mCamera.cancelAutoFocus(); | ||||
|             Camera.Parameters params = mCamera.getParameters(); | ||||
|             int maxAF = params.getMaxNumFocusAreas(); | ||||
|             int maxAE = params.getMaxNumMeteringAreas(); | ||||
|             if (maxAF > 0) params.setFocusAreas(null); | ||||
|             if (maxAE > 0) params.setMeteringAreas(null); | ||||
|             applyDefaultFocus(params); // Revert to internal focus.
 | ||||
|             mCamera.setParameters(params); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     Camera1(CameraView.CameraCallbacks callback) { | ||||
|         super(callback); | ||||
|         mMapper = new Mapper.Mapper1(); | ||||
|     } | ||||
| 
 | ||||
|     private void schedule(@Nullable final Task<Void> task, final boolean ensureAvailable, final Runnable action) { | ||||
|         mHandler.post(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (ensureAvailable && !isCameraAvailable()) { | ||||
|                     if (task != null) task.end(null); | ||||
|                 } else { | ||||
|                     action.run(); | ||||
|                     if (task != null) task.end(null); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // Preview surface is now available. If camera is open, set up.
 | ||||
|     @Override | ||||
|     public void onSurfaceAvailable() { | ||||
|         LOG.i("onSurfaceAvailable:", "Size is", mPreview.getSurfaceSize()); | ||||
|         schedule(null, false, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("onSurfaceAvailable:", "Inside handler. About to bind."); | ||||
|                 if (shouldBindToSurface()) bindToSurface(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // Preview surface did change its size. Compute a new preview size.
 | ||||
|     // This requires stopping and restarting the preview.
 | ||||
|     @Override | ||||
|     public void onSurfaceChanged() { | ||||
|         LOG.i("onSurfaceChanged, size is", mPreview.getSurfaceSize()); | ||||
|         schedule(null, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (!mIsBound) return; | ||||
| 
 | ||||
|                 // Compute a new camera preview size.
 | ||||
|                 Size newSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); | ||||
|                 if (newSize.equals(mPreviewSize)) return; | ||||
| 
 | ||||
|                 // Apply.
 | ||||
|                 LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on."); | ||||
|                 mPreviewSize = newSize; | ||||
|                 mCamera.stopPreview(); | ||||
|                 applySizesAndStartPreview("onSurfaceChanged:"); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private boolean shouldBindToSurface() { | ||||
|         return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsBound; | ||||
|     } | ||||
| 
 | ||||
|     // The act of binding an "open" camera to a "ready" preview.
 | ||||
|     // These can happen at different times but we want to end up here.
 | ||||
|     @WorkerThread | ||||
|     private void bindToSurface() { | ||||
|         LOG.i("bindToSurface:", "Started"); | ||||
|         Object output = mPreview.getOutput(); | ||||
|         try { | ||||
|             if (mPreview.getOutputClass() == SurfaceHolder.class) { | ||||
|                 mCamera.setPreviewDisplay((SurfaceHolder) output); | ||||
|             } else { | ||||
|                 mCamera.setPreviewTexture((SurfaceTexture) output); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             Log.e("bindToSurface:", "Failed to bind.", e); | ||||
|             throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW); | ||||
|         } | ||||
| 
 | ||||
|         mPictureSize = computePictureSize(); | ||||
|         mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); | ||||
|         applySizesAndStartPreview("bindToSurface:"); | ||||
|         mIsBound = true; | ||||
|     } | ||||
| 
 | ||||
|     // To be called when the preview size is setup or changed.
 | ||||
|     private void applySizesAndStartPreview(String log) { | ||||
|         LOG.i(log, "Dispatching onCameraPreviewSizeChanged."); | ||||
|         mCameraCallbacks.onCameraPreviewSizeChanged(); | ||||
| 
 | ||||
|         boolean invertPreviewSizes = shouldFlipSizes(); | ||||
|         mPreview.setDesiredSize( | ||||
|                 invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(), | ||||
|                 invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight() | ||||
|         ); | ||||
| 
 | ||||
|         Camera.Parameters params = mCamera.getParameters(); | ||||
|         mPreviewFormat = params.getPreviewFormat(); | ||||
|         params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview
 | ||||
|         params.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight()); // <- allowed
 | ||||
|         mCamera.setParameters(params); | ||||
| 
 | ||||
|         mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
 | ||||
|         mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
 | ||||
|         mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); | ||||
| 
 | ||||
|         LOG.i(log, "Starting preview with startPreview()."); | ||||
|         try { | ||||
|             mCamera.startPreview(); | ||||
|         } catch (Exception e) { | ||||
|             LOG.e(log, "Failed to start preview.", e); | ||||
|             throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW); | ||||
|         } | ||||
|         LOG.i(log, "Started preview."); | ||||
|     } | ||||
| 
 | ||||
|     @WorkerThread | ||||
|     @Override | ||||
|     void onStart() { | ||||
|         if (isCameraAvailable()) { | ||||
|             LOG.w("onStart:", "Camera not available. Should not happen."); | ||||
|             onStop(); // Should not happen.
 | ||||
|         } | ||||
|         if (collectCameraId()) { | ||||
|             try { | ||||
|                 mCamera = Camera.open(mCameraId); | ||||
|             } catch (Exception e) { | ||||
|                 LOG.e("onStart:", "Failed to connect. Maybe in use by another app?"); | ||||
|                 throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT); | ||||
|             } | ||||
|             mCamera.setErrorCallback(this); | ||||
| 
 | ||||
|             // Set parameters that might have been set before the camera was opened.
 | ||||
|             LOG.i("onStart:", "Applying default parameters."); | ||||
|             Camera.Parameters params = mCamera.getParameters(); | ||||
|             mExtraProperties = new ExtraProperties(params); | ||||
|             mCameraOptions = new CameraOptions(params, shouldFlipSizes()); | ||||
|             applyDefaultFocus(params); | ||||
|             mergeFlash(params, Flash.DEFAULT); | ||||
|             mergeLocation(params, null); | ||||
|             mergeWhiteBalance(params, WhiteBalance.DEFAULT); | ||||
|             mergeHdr(params, Hdr.DEFAULT); | ||||
|             mergePlaySound(mPlaySounds); | ||||
|             params.setRecordingHint(mSessionType == SessionType.VIDEO); | ||||
|             mCamera.setParameters(params); | ||||
| 
 | ||||
|             // Try starting preview.
 | ||||
|             mCamera.setDisplayOrientation(computeSensorToViewOffset()); // <- not allowed during preview
 | ||||
|             if (shouldBindToSurface()) bindToSurface(); | ||||
|             LOG.i("onStart:", "Ended"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @WorkerThread | ||||
|     @Override | ||||
|     void onStop() { | ||||
|         LOG.i("onStop:", "About to clean up."); | ||||
|         mHandler.get().removeCallbacks(mPostFocusResetRunnable); | ||||
|         mFrameManager.release(); | ||||
| 
 | ||||
|         if (mCamera != null) { | ||||
|             LOG.i("onStop:", "Clean up.", "Ending video."); | ||||
|             endVideoImmediately(); | ||||
| 
 | ||||
|             try { | ||||
|                 LOG.i("onStop:", "Clean up.", "Stopping preview."); | ||||
|                 mCamera.setPreviewCallbackWithBuffer(null); | ||||
|                 mCamera.stopPreview(); | ||||
|                 LOG.i("onStop:", "Clean up.", "Stopped preview."); | ||||
|             } catch (Exception e) { | ||||
|                 LOG.w("onStop:", "Clean up.", "Exception while stopping preview.", e); | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 LOG.i("onStop:", "Clean up.", "Releasing camera."); | ||||
|                 mCamera.release(); | ||||
|                 LOG.i("onStop:", "Clean up.", "Released camera."); | ||||
|             } catch (Exception e) { | ||||
|                 LOG.w("onStop:", "Clean up.", "Exception while releasing camera.", e); | ||||
|             } | ||||
|         } | ||||
|         mExtraProperties = null; | ||||
|         mCameraOptions = null; | ||||
|         mCamera = null; | ||||
|         mPreviewSize = null; | ||||
|         mPictureSize = null; | ||||
|         mIsBound = false; | ||||
|         mIsCapturingImage = false; | ||||
|         mIsCapturingVideo = false; | ||||
|         LOG.w("onStop:", "Clean up.", "Returning."); | ||||
| 
 | ||||
|         // We were saving a reference to the exception here and throwing to the user.
 | ||||
|         // I don't think it's correct. We are closing and have already done our best
 | ||||
|         // to clean up resources. No need to throw.
 | ||||
|         // if (error != null) throw new CameraException(error);
 | ||||
|     } | ||||
| 
 | ||||
|     private boolean collectCameraId() { | ||||
|         int internalFacing = mMapper.map(mFacing); | ||||
|         Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); | ||||
|         for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { | ||||
|             Camera.getCameraInfo(i, cameraInfo); | ||||
|             if (cameraInfo.facing == internalFacing) { | ||||
|                 mSensorOffset = cameraInfo.orientation; | ||||
|                 mCameraId = i; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBufferAvailable(byte[] buffer) { | ||||
|         // TODO: sync with handler?
 | ||||
|         if (isCameraAvailable()) { | ||||
|             mCamera.addCallbackBuffer(buffer); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onError(int error, Camera camera) { | ||||
|         if (error == Camera.CAMERA_ERROR_SERVER_DIED) { | ||||
|             // Looks like this is recoverable.
 | ||||
|             LOG.w("Recoverable error inside the onError callback.", "CAMERA_ERROR_SERVER_DIED"); | ||||
|             stopImmediately(); | ||||
|             start(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         LOG.e("Error inside the onError callback.", error); | ||||
|         Exception runtime = new RuntimeException(CameraLogger.lastMessage); | ||||
|         int reason; | ||||
|         switch (error) { | ||||
|             case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break; | ||||
|             case Camera.CAMERA_ERROR_UNKNOWN: reason = CameraException.REASON_UNKNOWN; break; | ||||
|             default: reason = CameraException.REASON_UNKNOWN; | ||||
|         } | ||||
|         throw new CameraException(runtime, reason); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setSessionType(SessionType sessionType) { | ||||
|         if (sessionType != mSessionType) { | ||||
|             mSessionType = sessionType; | ||||
|             schedule(null, true, new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     restart(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setLocation(Location location) { | ||||
|         final Location oldLocation = mLocation; | ||||
|         mLocation = location; | ||||
|         schedule(mLocationTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 if (mergeLocation(params, oldLocation)) mCamera.setParameters(params); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private boolean mergeLocation(Camera.Parameters params, Location oldLocation) { | ||||
|         if (mLocation != null) { | ||||
|             params.setGpsLatitude(mLocation.getLatitude()); | ||||
|             params.setGpsLongitude(mLocation.getLongitude()); | ||||
|             params.setGpsAltitude(mLocation.getAltitude()); | ||||
|             params.setGpsTimestamp(mLocation.getTime()); | ||||
|             params.setGpsProcessingMethod(mLocation.getProvider()); | ||||
| 
 | ||||
|             if (mIsCapturingVideo && mMediaRecorder != null) { | ||||
|                 mMediaRecorder.setLocation((float) mLocation.getLatitude(), | ||||
|                         (float) mLocation.getLongitude()); | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFacing(Facing facing) { | ||||
|         if (facing != mFacing) { | ||||
|             mFacing = facing; | ||||
|             schedule(null, true, new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     if (collectCameraId()) { | ||||
|                         restart(); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setWhiteBalance(WhiteBalance whiteBalance) { | ||||
|         final WhiteBalance old = mWhiteBalance; | ||||
|         mWhiteBalance = whiteBalance; | ||||
|         schedule(mWhiteBalanceTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 if (mergeWhiteBalance(params, old)) mCamera.setParameters(params); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { | ||||
|         if (mCameraOptions.supports(mWhiteBalance)) { | ||||
|             params.setWhiteBalance((String) mMapper.map(mWhiteBalance)); | ||||
|             return true; | ||||
|         } | ||||
|         mWhiteBalance = oldWhiteBalance; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setHdr(Hdr hdr) { | ||||
|         final Hdr old = mHdr; | ||||
|         mHdr = hdr; | ||||
|         schedule(mHdrTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 if (mergeHdr(params, old)) mCamera.setParameters(params); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { | ||||
|         if (mCameraOptions.supports(mHdr)) { | ||||
|             params.setSceneMode((String) mMapper.map(mHdr)); | ||||
|             return true; | ||||
|         } | ||||
|         mHdr = oldHdr; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @TargetApi(17) | ||||
|     private boolean mergePlaySound(boolean oldPlaySound) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | ||||
|             Camera.CameraInfo info = new Camera.CameraInfo(); | ||||
|             Camera.getCameraInfo(mCameraId, info); | ||||
|             if (info.canDisableShutterSound) { | ||||
|                 mCamera.enableShutterSound(mPlaySounds); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         if (mPlaySounds) { | ||||
|             return true; | ||||
|         } | ||||
|         mPlaySounds = oldPlaySound; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void setAudio(Audio audio) { | ||||
|         if (mAudio != audio) { | ||||
|             if (mIsCapturingVideo) { | ||||
|                 LOG.w("Audio setting was changed while recording. " + | ||||
|                         "Changes will take place starting from next video"); | ||||
|             } | ||||
|             mAudio = audio; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFlash(Flash flash) { | ||||
|         final Flash old = mFlash; | ||||
|         mFlash = flash; | ||||
|         schedule(mFlashTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 if (mergeFlash(params, old)) mCamera.setParameters(params); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) { | ||||
|         if (mCameraOptions.supports(mFlash)) { | ||||
|             params.setFlashMode((String) mMapper.map(mFlash)); | ||||
|             return true; | ||||
|         } | ||||
|         mFlash = oldFlash; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // Choose the best default focus, based on session type.
 | ||||
|     private void applyDefaultFocus(Camera.Parameters params) { | ||||
|         List<String> modes = params.getSupportedFocusModes(); | ||||
| 
 | ||||
|         if (mSessionType == SessionType.VIDEO && | ||||
|                 modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { | ||||
|             params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { | ||||
|             params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { | ||||
|             params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { | ||||
|             params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void setVideoQuality(VideoQuality videoQuality) { | ||||
|         final VideoQuality old = mVideoQuality; | ||||
|         mVideoQuality = videoQuality; | ||||
|         schedule(mVideoQualityTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (mIsCapturingVideo) { | ||||
|                     // TODO: actually any call to getParameters() could fail while recording a video.
 | ||||
|                     // See. https://stackoverflow.com/questions/14941625/
 | ||||
|                     mVideoQuality = old; | ||||
|                     throw new IllegalStateException("Can't change video quality while recording a video."); | ||||
|                 } | ||||
| 
 | ||||
|                 if (mSessionType == SessionType.VIDEO) { | ||||
|                     // Change capture size to a size that fits the video aspect ratio.
 | ||||
|                     Size oldSize = mPictureSize; | ||||
|                     mPictureSize = computePictureSize(); | ||||
|                     if (!mPictureSize.equals(oldSize)) { | ||||
|                         // New video quality triggers a new aspect ratio.
 | ||||
|                         // Go on and see if preview size should change also.
 | ||||
|                         Camera.Parameters params = mCamera.getParameters(); | ||||
|                         params.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight()); | ||||
|                         mCamera.setParameters(params); | ||||
|                         onSurfaceChanged(); | ||||
|                     } | ||||
|                     LOG.i("setVideoQuality:", "captureSize:", mPictureSize); | ||||
|                     LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void capturePicture() { | ||||
|         LOG.v("capturePicture: scheduling"); | ||||
|         schedule(null, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.v("capturePicture: performing.", mIsCapturingImage); | ||||
|                 if (mIsCapturingImage) return; | ||||
|                 if (mIsCapturingVideo && !mCameraOptions.isVideoSnapshotSupported()) return; | ||||
| 
 | ||||
|                 mIsCapturingImage = true; | ||||
|                 final int sensorToOutput = computeSensorToOutputOffset(); | ||||
|                 final int sensorToView = computeSensorToViewOffset(); | ||||
|                 final boolean outputMatchesView = (sensorToOutput + sensorToView + 180) % 180 == 0; | ||||
|                 final boolean outputFlip = mFacing == Facing.FRONT; | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 params.setRotation(sensorToOutput); | ||||
|                 mCamera.setParameters(params); | ||||
|                 mCamera.takePicture( | ||||
|                         new Camera.ShutterCallback() { | ||||
|                             @Override | ||||
|                             public void onShutter() { | ||||
|                                 mCameraCallbacks.onShutter(false); | ||||
|                             } | ||||
|                         }, | ||||
|                         null, | ||||
|                         null, | ||||
|                         new Camera.PictureCallback() { | ||||
|                             @Override | ||||
|                             public void onPictureTaken(byte[] data, final Camera camera) { | ||||
|                                 mIsCapturingImage = false; | ||||
|                                 mCameraCallbacks.processImage(data, outputMatchesView, outputFlip); | ||||
|                                 camera.startPreview(); // This is needed, read somewhere in the docs.
 | ||||
|                             } | ||||
|                         } | ||||
|                 ); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void captureSnapshot() { | ||||
|         LOG.v("captureSnapshot: scheduling"); | ||||
|         schedule(null, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.v("captureSnapshot: performing.", mIsCapturingImage); | ||||
|                 if (mIsCapturingImage) return; | ||||
|                 // This won't work while capturing a video.
 | ||||
|                 // Switch to capturePicture.
 | ||||
|                 if (mIsCapturingVideo) { | ||||
|                     capturePicture(); | ||||
|                     return; | ||||
|                 } | ||||
|                 mIsCapturingImage = true; | ||||
|                 mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { | ||||
|                     @Override | ||||
|                     public void onPreviewFrame(final byte[] data, Camera camera) { | ||||
|                         mCameraCallbacks.onShutter(true); | ||||
| 
 | ||||
|                         // Got to rotate the preview frame, since byte[] data here does not include
 | ||||
|                         // EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
 | ||||
|                         // Adding EXIF to a byte array, unfortunately, is hard.
 | ||||
|                         final int sensorToOutput = computeSensorToOutputOffset(); | ||||
|                         final int sensorToView = computeSensorToViewOffset(); | ||||
|                         final boolean outputMatchesView = (sensorToOutput + sensorToView + 180) % 180 == 0; | ||||
|                         final boolean outputFlip = mFacing == Facing.FRONT; | ||||
|                         final boolean flip = sensorToOutput % 180 != 0; | ||||
|                         final int preWidth = mPreviewSize.getWidth(); | ||||
|                         final int preHeight = mPreviewSize.getHeight(); | ||||
|                         final int postWidth = flip ? preHeight : preWidth; | ||||
|                         final int postHeight = flip ? preWidth : preHeight; | ||||
|                         final int format = mPreviewFormat; | ||||
|                         WorkerHandler.run(new Runnable() { | ||||
|                             @Override | ||||
|                             public void run() { | ||||
| 
 | ||||
|                                 LOG.v("captureSnapshot: rotating."); | ||||
|                                 byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToOutput); | ||||
|                                 LOG.v("captureSnapshot: rotated."); | ||||
|                                 YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); | ||||
|                                 mCameraCallbacks.processSnapshot(yuv, outputMatchesView, outputFlip); | ||||
|                                 mIsCapturingImage = false; | ||||
|                             } | ||||
|                         }); | ||||
| 
 | ||||
|                         // It seems that the buffers are already cleared here, so we need to allocate again.
 | ||||
|                         mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
 | ||||
|                         mCamera.setPreviewCallbackWithBuffer(Camera1.this); // Add ourselves
 | ||||
|                         mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onPreviewFrame(byte[] data, Camera camera) { | ||||
|         Frame frame = mFrameManager.getFrame(data, | ||||
|                 System.currentTimeMillis(), | ||||
|                 computeSensorToOutputOffset(), | ||||
|                 mPreviewSize, | ||||
|                 mPreviewFormat); | ||||
|         mCameraCallbacks.dispatchFrame(frame); | ||||
|     } | ||||
| 
 | ||||
|     private boolean isCameraAvailable() { | ||||
|         switch (mState) { | ||||
|             // If we are stopped, don't.
 | ||||
|             case STATE_STOPPED: | ||||
|                 return false; | ||||
|             // If we are going to be closed, don't act on camera.
 | ||||
|             // Even if mCamera != null, it might have been released.
 | ||||
|             case STATE_STOPPING: | ||||
|                 return false; | ||||
|             // If we are started, mCamera should never be null.
 | ||||
|             case STATE_STARTED: | ||||
|                 return true; | ||||
|             // If we are starting, theoretically we could act.
 | ||||
|             // Just check that camera is available.
 | ||||
|             case STATE_STARTING: | ||||
|                 return mCamera != null; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // -----------------
 | ||||
|     // Video recording stuff.
 | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void startVideo(@NonNull final File videoFile) { | ||||
|         schedule(mStartVideoTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (mIsCapturingVideo) return; | ||||
|                 if (mSessionType == SessionType.VIDEO) { | ||||
|                     mVideoFile = videoFile; | ||||
|                     mIsCapturingVideo = true; | ||||
|                     initMediaRecorder(); | ||||
|                     try { | ||||
|                         mMediaRecorder.prepare(); | ||||
|                         mMediaRecorder.start(); | ||||
|                     } catch (Exception e) { | ||||
|                         LOG.e("Error while starting MediaRecorder. Swallowing.", e); | ||||
|                         mVideoFile = null; | ||||
|                         mCamera.lock(); | ||||
|                         endVideoImmediately(); | ||||
|                     } | ||||
|                 } else { | ||||
|                     throw new IllegalStateException("Can't record video while session type is picture"); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void endVideo() { | ||||
|         schedule(null, false, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 endVideoImmediately(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @WorkerThread | ||||
|     private void endVideoImmediately() { | ||||
|         LOG.i("endVideoImmediately:", "is capturing:", mIsCapturingVideo); | ||||
|         mIsCapturingVideo = false; | ||||
|         if (mMediaRecorder != null) { | ||||
|             try { | ||||
|                 mMediaRecorder.stop(); | ||||
|             } catch (Exception e) { | ||||
|                 // This can happen if endVideo() is called right after startVideo(). We don't care.
 | ||||
|                 LOG.w("endVideoImmediately:", "Error while closing media recorder. Swallowing", e); | ||||
|             } | ||||
|             mMediaRecorder.release(); | ||||
|             mMediaRecorder = null; | ||||
|         } | ||||
|         if (mVideoFile != null) { | ||||
|             mCameraCallbacks.dispatchOnVideoTaken(mVideoFile); | ||||
|             mVideoFile = null; | ||||
|         } | ||||
|         if (mCamera != null) { | ||||
|             // This is needed to restore FrameProcessor. No re-allocation needed though.
 | ||||
|             mCamera.setPreviewCallbackWithBuffer(this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @WorkerThread | ||||
|     private void initMediaRecorder() { | ||||
|         mMediaRecorder = new MediaRecorder(); | ||||
|         mCamera.unlock(); | ||||
|         mMediaRecorder.setCamera(mCamera); | ||||
| 
 | ||||
|         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); | ||||
|         if (mAudio == Audio.ON) { | ||||
|             // Must be called before setOutputFormat.
 | ||||
|             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); | ||||
|         } | ||||
|         CamcorderProfile profile = getCamcorderProfile(); | ||||
|         mMediaRecorder.setOutputFormat(profile.fileFormat); | ||||
|         mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); | ||||
|         mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); | ||||
|         if (mVideoCodec == VideoCodec.DEFAULT) { | ||||
|             mMediaRecorder.setVideoEncoder(profile.videoCodec); | ||||
|         } else { | ||||
|             mMediaRecorder.setVideoEncoder(mMapper.map(mVideoCodec)); | ||||
|         } | ||||
|         mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); | ||||
|         if (mAudio == Audio.ON) { | ||||
|             mMediaRecorder.setAudioChannels(profile.audioChannels); | ||||
|             mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate); | ||||
|             mMediaRecorder.setAudioEncoder(profile.audioCodec); | ||||
|             mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate); | ||||
|         } | ||||
| 
 | ||||
|         if (mLocation != null) { | ||||
|             mMediaRecorder.setLocation( | ||||
|                     (float) mLocation.getLatitude(), | ||||
|                     (float) mLocation.getLongitude()); | ||||
|         } | ||||
| 
 | ||||
|         mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); | ||||
|         mMediaRecorder.setOrientationHint(computeSensorToOutputOffset()); | ||||
| 
 | ||||
|         mMediaRecorder.setMaxFileSize(mVideoMaxSize); | ||||
|         mMediaRecorder.setMaxDuration(mVideoMaxDuration); | ||||
| 
 | ||||
|         mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { | ||||
|             @Override | ||||
|             public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { | ||||
|                 switch (what) { | ||||
|                     case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: | ||||
|                     case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: | ||||
|                         endVideoImmediately(); | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface());
 | ||||
|     } | ||||
| 
 | ||||
|     // -----------------
 | ||||
|     // Zoom and simpler stuff.
 | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void setZoom(final float zoom, final PointF[] points, final boolean notify) { | ||||
|         schedule(mZoomTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (!mCameraOptions.isZoomSupported()) return; | ||||
| 
 | ||||
|                 mZoomValue = zoom; | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 float max = params.getMaxZoom(); | ||||
|                 params.setZoom((int) (zoom * max)); | ||||
|                 mCamera.setParameters(params); | ||||
| 
 | ||||
|                 if (notify) { | ||||
|                     mCameraCallbacks.dispatchOnZoomChanged(zoom, points); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setExposureCorrection(final float EVvalue, final float[] bounds, | ||||
|                                final PointF[] points, final boolean notify) { | ||||
|         schedule(mExposureCorrectionTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (!mCameraOptions.isExposureCorrectionSupported()) return; | ||||
| 
 | ||||
|                 float value = EVvalue; | ||||
|                 float max = mCameraOptions.getExposureCorrectionMaxValue(); | ||||
|                 float min = mCameraOptions.getExposureCorrectionMinValue(); | ||||
|                 value = value < min ? min : value > max ? max : value; // cap
 | ||||
|                 mExposureCorrectionValue = value; | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 int indexValue = (int) (value / params.getExposureCompensationStep()); | ||||
|                 params.setExposureCompensation(indexValue); | ||||
|                 mCamera.setParameters(params); | ||||
| 
 | ||||
|                 if (notify) { | ||||
|                     mCameraCallbacks.dispatchOnExposureCorrectionChanged(value, bounds, points); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // -----------------
 | ||||
|     // Tap to focus stuff.
 | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     void startAutoFocus(@Nullable final Gesture gesture, final PointF point) { | ||||
|         // Must get width and height from the UI thread.
 | ||||
|         int viewWidth = 0, viewHeight = 0; | ||||
|         if (mPreview != null && mPreview.isReady()) { | ||||
|             viewWidth = mPreview.getView().getWidth(); | ||||
|             viewHeight = mPreview.getView().getHeight(); | ||||
|         } | ||||
|         final int viewWidthF = viewWidth; | ||||
|         final int viewHeightF = viewHeight; | ||||
|         // Schedule.
 | ||||
|         schedule(null, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 if (!mCameraOptions.isAutoFocusSupported()) return; | ||||
|                 final PointF p = new PointF(point.x, point.y); // copy.
 | ||||
|                 List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y, | ||||
|                         viewWidthF, viewHeightF, computeSensorToViewOffset()); | ||||
|                 List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1); | ||||
| 
 | ||||
|                 // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().
 | ||||
|                 Camera.Parameters params = mCamera.getParameters(); | ||||
|                 int maxAF = params.getMaxNumFocusAreas(); | ||||
|                 int maxAE = params.getMaxNumMeteringAreas(); | ||||
|                 if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1); | ||||
|                 if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); | ||||
|                 params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); | ||||
|                 mCamera.setParameters(params); | ||||
|                 mCameraCallbacks.dispatchOnFocusStart(gesture, p); | ||||
|                 // TODO this is not guaranteed to be called... Fix.
 | ||||
|                 try { | ||||
|                     mCamera.autoFocus(new Camera.AutoFocusCallback() { | ||||
|                         @Override | ||||
|                         public void onAutoFocus(boolean success, Camera camera) { | ||||
|                             // TODO lock auto exposure and white balance for a while
 | ||||
|                             mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); | ||||
|                             mHandler.get().removeCallbacks(mPostFocusResetRunnable); | ||||
|                             mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); | ||||
|                         } | ||||
|                     }); | ||||
|                 } catch (RuntimeException e) { | ||||
|                     // Handling random auto-focus exception on some devices
 | ||||
|                     // See https://github.com/natario1/CameraView/issues/181
 | ||||
|                     LOG.e("startAutoFocus:", "Error calling autoFocus", e); | ||||
|                     mCameraCallbacks.dispatchOnFocusEnd(gesture, false, p); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @WorkerThread | ||||
|     private static List<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY, | ||||
|                                                           int viewWidth, int viewHeight, | ||||
|                                                           int sensorToDisplay) { | ||||
|         // Event came in view coordinates. We must rotate to sensor coordinates.
 | ||||
|         // First, rescale to the -1000 ... 1000 range.
 | ||||
|         int displayToSensor = -sensorToDisplay; | ||||
|         viewClickX = -1000d + (viewClickX / (double) viewWidth) * 2000d; | ||||
|         viewClickY = -1000d + (viewClickY / (double) viewHeight) * 2000d; | ||||
| 
 | ||||
|         // Apply rotation to this point.
 | ||||
|         // https://academo.org/demos/rotation-about-point/
 | ||||
|         double theta = ((double) displayToSensor) * Math.PI / 180; | ||||
|         double sensorClickX = viewClickX * Math.cos(theta) - viewClickY * Math.sin(theta); | ||||
|         double sensorClickY = viewClickX * Math.sin(theta) + viewClickY * Math.cos(theta); | ||||
|         LOG.i("focus:", "viewClickX:", viewClickX, "viewClickY:", viewClickY); | ||||
|         LOG.i("focus:", "sensorClickX:", sensorClickX, "sensorClickY:", sensorClickY); | ||||
| 
 | ||||
|         // Compute the rect bounds.
 | ||||
|         Rect rect1 = computeMeteringArea(sensorClickX, sensorClickY, 150d); | ||||
|         int weight1 = 1000; // 150 * 150 * 1000 = more than 10.000.000
 | ||||
|         Rect rect2 = computeMeteringArea(sensorClickX, sensorClickY, 300d); | ||||
|         int weight2 = 100; // 300 * 300 * 100 = 9.000.000
 | ||||
| 
 | ||||
|         List<Camera.Area> list = new ArrayList<>(2); | ||||
|         list.add(new Camera.Area(rect1, weight1)); | ||||
|         list.add(new Camera.Area(rect2, weight2)); | ||||
|         return list; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private static Rect computeMeteringArea(double centerX, double centerY, double size) { | ||||
|         double delta = size / 2d; | ||||
|         int top = (int) Math.max(centerY - delta, -1000); | ||||
|         int bottom = (int) Math.min(centerY + delta, 1000); | ||||
|         int left = (int) Math.max(centerX - delta, -1000); | ||||
|         int right = (int) Math.min(centerX + delta, 1000); | ||||
|         LOG.i("focus:", "computeMeteringArea:", "top:", top, "left:", left, "bottom:", bottom, "right:", right); | ||||
|         return new Rect(left, top, right, bottom); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // -----------------
 | ||||
|     // Size stuff.
 | ||||
| 
 | ||||
| 
 | ||||
|     @Nullable | ||||
|     private List<Size> sizesFromList(List<Camera.Size> sizes) { | ||||
|         if (sizes == null) return null; | ||||
|         List<Size> result = new ArrayList<>(sizes.size()); | ||||
|         for (Camera.Size size : sizes) { | ||||
|             Size add = new Size(size.width, size.height); | ||||
|             if (!result.contains(add)) result.add(add); | ||||
|         } | ||||
|         LOG.i("size:", "sizesFromList:", result); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setPlaySounds(boolean playSounds) { | ||||
|         final boolean old = mPlaySounds; | ||||
|         mPlaySounds = playSounds; | ||||
|         schedule(mPlaySoundsTask, true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 mergePlaySound(old); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // -----------------
 | ||||
|     // Additional helper info
 | ||||
| } | ||||
| 
 | ||||
| @ -1,122 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.graphics.PointF; | ||||
| import android.location.Location; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| @TargetApi(21) | ||||
| class Camera2 extends CameraController { | ||||
| 
 | ||||
|     public Camera2(CameraView.CameraCallbacks callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceAvailable() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceChanged() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStart() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStop() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setSessionType(SessionType sessionType) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFacing(Facing facing) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setZoom(float zoom, PointF[] points, boolean notify) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setExposureCorrection(float EVvalue, float[] bounds, PointF[] points, boolean notify) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFlash(Flash flash) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setWhiteBalance(WhiteBalance whiteBalance) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setHdr(Hdr hdr) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setAudio(Audio audio) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setLocation(Location location) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setVideoQuality(VideoQuality videoQuality) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void capturePicture() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void captureSnapshot() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void startVideo(@NonNull File file) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void endVideo() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void startAutoFocus(@Nullable Gesture gesture, PointF point) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBufferAvailable(byte[] buffer) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setPlaySounds(boolean playSounds) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue