Compare commits
No commits in common. 'main' and 'legacy-v1' have entirely different histories.
@ -1 +0,0 @@ |
|||||||
Contributing guidelines are [hosted here](https://natario1.github.io/CameraView/extra/contributing). |
|
@ -1,12 +0,0 @@ |
|||||||
# 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,14 +1,10 @@ |
|||||||
### Before you go |
### Before you go |
||||||
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that |
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. |
||||||
we can discuss the best approach to address the problem. Without a reference issue and discussion, |
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. |
||||||
unfortunately, this PR will likely be ignored. |
|
||||||
|
|
||||||
If the edited files were covered by tests, updated tests are required for merging. |
- Fixes ... (*issue number*) |
||||||
Please look into the tests folders and make sure you cover new code. |
|
||||||
|
|
||||||
- Fixes: ... (*issue number*) |
|
||||||
- Tests: ... (*yes/no*) |
- Tests: ... (*yes/no*) |
||||||
- Docs updated: ... (*yes/no*) |
- Docs updated: ... (*yes/no*) |
||||||
|
|
||||||
### Solution |
### Solution |
||||||
If applicable, describe briefly how the issue was addressed. |
If applicable, briefly describe how the issue was addressed. |
||||||
|
@ -1,119 +0,0 @@ |
|||||||
# 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: |
|
||||||
- main |
|
||||||
pull_request: |
|
||||||
jobs: |
|
||||||
ANDROID_BASE_CHECKS: |
|
||||||
name: Base Checks |
|
||||||
runs-on: ubuntu-latest |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- 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@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- 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@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Execute emulator tests |
|
||||||
timeout-minutes: 30 |
|
||||||
uses: reactivecircus/android-emulator-runner@v2.21.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 |
|
||||||
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@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- 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 |
|
@ -1,23 +0,0 @@ |
|||||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions |
|
||||||
name: Deploy |
|
||||||
on: |
|
||||||
release: |
|
||||||
types: [published] |
|
||||||
jobs: |
|
||||||
MAVEN_UPLOAD: |
|
||||||
name: Maven Upload |
|
||||||
runs-on: ubuntu-latest |
|
||||||
env: |
|
||||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} |
|
||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} |
|
||||||
SONATYPE_USER: ${{ secrets.SONATYPE_USER }} |
|
||||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Perform maven upload |
|
||||||
run: ./gradlew publishToSonatype |
|
@ -1,14 +0,0 @@ |
|||||||
#!/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,25 +0,0 @@ |
|||||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions |
|
||||||
# Renaming ? Change the README badge. |
|
||||||
name: Snapshot |
|
||||||
on: |
|
||||||
push: |
|
||||||
branches: |
|
||||||
- main |
|
||||||
jobs: |
|
||||||
SNAPSHOT: |
|
||||||
name: Publish Snapshot |
|
||||||
runs-on: ubuntu-latest |
|
||||||
env: |
|
||||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} |
|
||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} |
|
||||||
SONATYPE_USER: ${{ secrets.SONATYPE_USER }} |
|
||||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Publish sonatype snapshot |
|
||||||
run: ./gradlew publishToSonatypeSnapshot |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="runAndroidTests" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value="runAndroidTests" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" value="" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="runUnitTests" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value="runUnitTests" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" value="" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -0,0 +1,86 @@ |
|||||||
|
# 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 |
@ -0,0 +1,156 @@ |
|||||||
|
## 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 |
@ -1,166 +1,782 @@ |
|||||||
[![Build Status](https://github.com/natario1/CameraView/workflows/Build/badge.svg?event=push)](https://github.com/natario1/CameraView/actions) |
[![Build Status](https://travis-ci.org/natario1/CameraView.svg?branch=master)](https://travis-ci.org/natario1/CameraView) |
||||||
[![Code Coverage](https://codecov.io/gh/natario1/CameraView/branch/main/graph/badge.svg)](https://codecov.io/gh/natario1/CameraView) |
[![Code Coverage](https://codecov.io/gh/natario1/CameraView/branch/master/graph/badge.svg)](https://codecov.io/gh/natario1/CameraView) |
||||||
[![Release](https://img.shields.io/github/release/natario1/CameraView.svg)](https://github.com/natario1/CameraView/releases) |
|
||||||
[![Issues](https://img.shields.io/github/issues-raw/natario1/CameraView.svg)](https://github.com/natario1/CameraView/issues) |
|
||||||
[![Funding](https://img.shields.io/opencollective/all/CameraView.svg?colorB=r)](https://natario1.github.io/CameraView/extra/donate) |
|
||||||
|
|
||||||
⠀ <!-- Hack to add whitespace --> |
|
||||||
|
|
||||||
<p align="center"> |
<p align="center"> |
||||||
<img src="docs/static/banner.png" width="100%"> |
<img src="art/icon.png" vspace="10" width="250" height="250"> |
||||||
</p> |
</p> |
||||||
|
|
||||||
*Post-processing videos or want to reduce video size before uploading? Take a look at our [Transcoder](https://github.com/natario1/Transcoder).* |
|
||||||
|
|
||||||
*Like the project, make profit from it, or simply want to thank back? Please consider [sponsoring me](https://github.com/sponsors/natario1) or [donating](https://natario1.github.io/CameraView/extra/donate)!* |
|
||||||
|
|
||||||
*Need support, consulting, or have any other business-related question? Feel free to <a href="mailto:mat.iavarone@gmail.com">get in touch</a>.* |
|
||||||
|
|
||||||
# CameraView |
# CameraView |
||||||
|
|
||||||
CameraView is a well documented, high-level library that makes capturing pictures and videos easy, |
CameraView is a well documented, high-level library that makes capturing pictures and videos easy, |
||||||
addressing most of the common issues and needs, and still leaving you with flexibility where needed. |
addressing most of the common issues and needs, and still leaving you with flexibility where needed. |
||||||
|
See [CHANGELOG](https://github.com/natario1/CameraView/blob/master/CHANGELOG.md). |
||||||
|
|
||||||
```groovy |
```groovy |
||||||
api 'com.otaliastudios:cameraview:2.7.2' |
compile 'com.otaliastudios:cameraview:1.6.1' |
||||||
``` |
``` |
||||||
|
|
||||||
- Fast & reliable |
|
||||||
- Gestures support [[docs]](https://natario1.github.io/CameraView/docs/gestures) |
|
||||||
- Real-time filters [[docs]](https://natario1.github.io/CameraView/docs/filters) |
|
||||||
- Camera1 or Camera2 powered engine [[docs]](https://natario1.github.io/CameraView/docs/previews) |
|
||||||
- Frame processing support [[docs]](https://natario1.github.io/CameraView/docs/frame-processing) |
|
||||||
- Watermarks & animated overlays [[docs]](https://natario1.github.io/CameraView/docs/watermarks-and-overlays) |
|
||||||
- OpenGL powered preview [[docs]](https://natario1.github.io/CameraView/docs/previews) |
|
||||||
- Take high-quality content with `takePicture` and `takeVideo` [[docs]](https://natario1.github.io/CameraView/docs/capturing-media) |
|
||||||
- Take super-fast snapshots with `takePictureSnapshot` and `takeVideoSnapshot` [[docs]](https://natario1.github.io/CameraView/docs/capturing-media) |
|
||||||
- Smart sizing: create a `CameraView` of any size [[docs]](https://natario1.github.io/CameraView/docs/preview-size) |
|
||||||
- Control HDR, flash, zoom, white balance, exposure, location, grid drawing & more [[docs]](https://natario1.github.io/CameraView/docs/controls) |
|
||||||
- RAW pictures support [[docs]](https://natario1.github.io/CameraView/docs/controls) |
|
||||||
- Lightweight |
|
||||||
- Works down to API level 15 |
|
||||||
- Well tested |
|
||||||
|
|
||||||
⠀ <!-- Hack to add whitespace --> |
Make sure your project repositories include the `jcenter()`: |
||||||
|
|
||||||
<p align="center"> |
```groovy |
||||||
<img src="docs/static/screen1.png" width="250" hspace="5"><img src="docs/static/screen2.png" width="250" hspace="5"><img src="docs/static/screen3.png" width="250" hspace="5"> |
allprojects { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
<p> |
||||||
|
<img src="art/screen1.jpg" width="250" vspace="20" hspace="5"> |
||||||
|
<img src="art/screen2.jpg" width="250" vspace="20" hspace="5"> |
||||||
|
<img src="art/screen3.jpg" width="250" vspace="20" hspace="5"> |
||||||
</p> |
</p> |
||||||
|
|
||||||
⠀ <!-- Hack to add whitespace --> |
*This was a fork of [CameraKit-Android](https://github.com/gogopop/CameraKit-Android), originally a |
||||||
|
fork of [Google's CameraView](https://github.com/google/cameraview), but has been |
||||||
|
[completely rewritten](https://github.com/natario1/CameraView/graphs/contributors?type=d). |
||||||
|
See below for a [list of what was done](#roadmap) and [licensing info](#contributing-and-licenses).* |
||||||
|
|
||||||
## Support |
### Features |
||||||
|
|
||||||
If you like the project, make profit from it, or simply want to thank back, please consider |
- Seamless image and video capturing |
||||||
[sponsoring me](https://github.com/sponsors/natario1) through the GitHub Sponsors program! You can |
- **Gestures** support (tap to focus, pinch to zoom and much more) |
||||||
have your company logo here, get private support hours or simply help me push this forward. |
- System permission handling |
||||||
If you prefer, you can also [donate](https://natario1.github.io/CameraView/extra/donate) |
- **Smart sizing** behavior |
||||||
to our OpenCollective page. |
- Preview: Create a `CameraView` of **any size** |
||||||
|
- Preview: Center inside or center crop behaviors |
||||||
|
- Output: Handy utilities to set the output size |
||||||
|
- Output: Automatic cropping to match your `CameraView` preview bounds |
||||||
|
- Built-in **grid drawing** |
||||||
|
- Multiple capture methods |
||||||
|
- Take high-resolution pictures with `capturePicture` |
||||||
|
- Take **quick snapshots** as a freeze frame of the preview with `captureSnapshot` |
||||||
|
- Control HDR, flash, zoom, white balance, exposure correction and more |
||||||
|
- **Frame processing** support |
||||||
|
- **Metadata** support for pictures and videos |
||||||
|
- Automatically detected orientation tags |
||||||
|
- Plug in **location tags** with `setLocation()` API |
||||||
|
- `CameraUtils` to help with Bitmaps and orientations |
||||||
|
- Error handling |
||||||
|
- Thread safe, **well tested** |
||||||
|
- **Lightweight**, no dependencies, just support `ExifInterface` |
||||||
|
- Works down to API level 15 |
||||||
|
|
||||||
CameraView is trusted and supported by [ShareChat](https://sharechat.com/), a social media app with over 100 million downloads. |
# Docs |
||||||
|
|
||||||
<p align="center"> |
- [Usage](#usage) |
||||||
<img src="docs/static/sharechat.png" width="100%"> |
- [Capturing Images](#capturing-images) |
||||||
</p> |
- [Capturing Video](#capturing-video) |
||||||
|
- [Other camera events](#other-camera-events) |
||||||
|
- [Gestures](#gestures) |
||||||
|
- [Sizing Behavior](#sizing-behavior) |
||||||
|
- [Preview Size](#preview-size) |
||||||
|
- [Picture Size](#picture-size) |
||||||
|
- [Camera Controls](#camera-controls) |
||||||
|
- [Frame Processing](#frame-processing) |
||||||
|
- [Other APIs](#other-apis) |
||||||
|
- [Permissions Behavior](#permissions-behavior) |
||||||
|
- [Logging](#logging) |
||||||
|
- [Device-specific issues](#device-specific-issues) |
||||||
|
- [Roadmap](#roadmap) |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
To use the CameraView engine, simply add a `CameraView` to your layout: |
||||||
|
|
||||||
|
```xml |
||||||
|
<com.otaliastudios.cameraview.CameraView |
||||||
|
android:id="@+id/camera" |
||||||
|
android:keepScreenOn="true" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" /> |
||||||
|
``` |
||||||
|
|
||||||
|
`CameraView` is a component bound to your activity or fragment lifecycle. This means that you must pass the |
||||||
|
lifecycle owner using `setLifecycleOwner`: |
||||||
|
|
||||||
|
```java |
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
CameraView camera = findViewById(R.id.camera); |
||||||
|
camera.setLifecycleOwner(this); |
||||||
|
// From fragments, use fragment.viewLifecycleOwner instead of this! |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
For those who are not using the support libraries and the lifecycle implementation, make sure you override `onResume`, |
||||||
|
`onPause` and `onDestroy` in your component, and call `CameraView.start()`, `stop()` |
||||||
|
and `destroy()`. |
||||||
|
|
||||||
Feel free to <a href="mailto:mat.iavarone@gmail.com">contact me</a> for support, consulting or any other business-related question. |
```java |
||||||
|
@Override |
||||||
|
protected void onResume() { |
||||||
|
super.onResume(); |
||||||
|
cameraView.start(); |
||||||
|
} |
||||||
|
|
||||||
Thanks to all our project backers... [[become a backer]](https://opencollective.com/cameraview#backer) |
@Override |
||||||
|
protected void onPause() { |
||||||
|
super.onPause(); |
||||||
|
cameraView.stop(); |
||||||
|
} |
||||||
|
|
||||||
<a href="https://opencollective.com/cameraview#backers" target="_blank"><img src="https://opencollective.com/cameraview/backers.svg?width=890"></a> |
@Override |
||||||
|
protected void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
cameraView.destroy(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### Capturing Images |
||||||
|
|
||||||
|
To capture an image just call `CameraView.capturePicture()`. Make sure you setup a `CameraListener` |
||||||
|
to handle the image callback. |
||||||
|
|
||||||
|
```java |
||||||
|
camera.addCameraListener(new CameraListener() { |
||||||
|
@Override |
||||||
|
public void onPictureTaken(byte[] picture) { |
||||||
|
// Create a bitmap or a file... |
||||||
|
// CameraUtils will read EXIF orientation for you, in a worker thread. |
||||||
|
CameraUtils.decodeBitmap(picture, ...); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
camera.capturePicture(); |
||||||
|
``` |
||||||
|
|
||||||
|
You can also use `camera.captureSnapshot()` to capture a preview frame. This is faster, though will |
||||||
|
ensure lower quality output. |
||||||
|
|
||||||
...and to all our project sponsors! [[become a sponsor]](https://opencollective.com/cameraview#sponsor) |
### Capturing Video |
||||||
|
|
||||||
|
To capture video just call `CameraView.startCapturingVideo(file)` to start, and |
||||||
|
`CameraView.stopCapturingVideo()` to finish. Make sure you setup a `CameraListener` to handle |
||||||
|
the video callback. |
||||||
|
|
||||||
|
```java |
||||||
|
camera.addCameraListener(new CameraListener() { |
||||||
|
@Override |
||||||
|
public void onVideoTaken(File video) { |
||||||
|
// The File is the same you passed before. |
||||||
|
// Now it holds a MP4 video. |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Select output file. Make sure you have write permissions. |
||||||
|
File file = ...; |
||||||
|
camera.startCapturingVideo(file); |
||||||
|
|
||||||
|
// Later... stop recording. This will trigger onVideoTaken(). |
||||||
|
camera.stopCapturingVideo(); |
||||||
|
|
||||||
|
// You can also use one of the video constraints: |
||||||
|
// videoMaxSize and videoMaxDuration will automatically stop recording when satisfied. |
||||||
|
camera.setVideoMaxSize(100000); |
||||||
|
camera.setVideoMaxDuration(5000); |
||||||
|
camera.startCapturingVideo(file); |
||||||
|
``` |
||||||
|
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/0/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/0/avatar.svg"></a> |
### Other camera events |
||||||
<a href="https://opencollective.com/cameraview/sponsor/1/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/1/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/2/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/2/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/3/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/3/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/4/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/4/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/5/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/5/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/6/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/6/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/7/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/7/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/8/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/8/avatar.svg"></a> |
|
||||||
<a href="https://opencollective.com/cameraview/sponsor/9/website" target="_blank"><img src="https://opencollective.com/cameraview/sponsor/9/avatar.svg"></a> |
|
||||||
|
|
||||||
## Setup |
Make sure you can react to different camera events by setting up one or more `CameraListener` |
||||||
|
instances. All these are executed on the UI thread. |
||||||
|
|
||||||
|
```java |
||||||
|
camera.addCameraListener(new CameraListener() { |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that the camera was opened. |
||||||
|
* The options object collects all supported options by the current camera. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onCameraOpened(CameraOptions options) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that the camera session was closed. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onCameraClosed() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies about an error during the camera setup or configuration. |
||||||
|
* At the moment, errors that are passed here are unrecoverable. When this is called, |
||||||
|
* the camera has been released and is presumably showing a black preview. |
||||||
|
* |
||||||
|
* This is the right moment to show an error dialog to the user. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onCameraError(CameraException error) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that a picture previously captured with capturePicture() |
||||||
|
* or captureSnapshot() is ready to be shown or saved. |
||||||
|
* |
||||||
|
* If planning to get a bitmap, you can use CameraUtils.decodeBitmap() |
||||||
|
* to decode the byte array taking care about orientation. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onPictureTaken(byte[] picture) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that a video capture has just ended. The file parameter is the one that |
||||||
|
* was passed to startCapturingVideo(File), or a fallback video file. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onVideoTaken(File video) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that the device was tilted or the window offset changed. |
||||||
|
* The orientation passed can be used to align views (e.g. buttons) to the current |
||||||
|
* camera viewport so they will appear correctly oriented to the user. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onOrientationChanged(int orientation) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that user interacted with the screen and started focus with a gesture, |
||||||
|
* and the autofocus is trying to focus around that area. |
||||||
|
* This can be used to draw things on screen. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onFocusStart(PointF point) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that a gesture focus event just ended, and the camera converged |
||||||
|
* to a new focus (and possibly exposure and white balance). |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onFocusEnd(boolean successful, PointF point) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Noitifies that a finger gesture just caused the camera zoom |
||||||
|
* to be changed. This can be used, for example, to draw a seek bar. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onZoomChanged(float newValue, float[] bounds, PointF[] fingers) {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Noitifies that a finger gesture just caused the camera exposure correction |
||||||
|
* to be changed. This can be used, for example, to draw a seek bar. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {} |
||||||
|
|
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
## Gestures |
||||||
|
|
||||||
|
`CameraView` listen to lots of different gestures inside its bounds. You have the chance to map |
||||||
|
these gestures to particular actions or camera controls, using `mapGesture()`. |
||||||
|
This lets you emulate typical behaviors in a single line: |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); // Pinch to zoom! |
||||||
|
cameraView.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER); // Tap to focus! |
||||||
|
cameraView.mapGesture(Gesture.LONG_TAP, GestureAction.CAPTURE); // Long tap to shoot! |
||||||
|
``` |
||||||
|
|
||||||
|
Simple as that. More gestures are coming. There are two things to be noted: |
||||||
|
|
||||||
|
- Not every mapping is valid. For example, you can't control zoom with long taps, or start focusing by pinching. |
||||||
|
- Some actions might not be supported by the sensor. Check out `CameraOptions` to know what's legit and what's not. |
||||||
|
|
||||||
|
|Gesture (XML)|Description|Can be mapped to| |
||||||
|
|-------------|-----------|----------------| |
||||||
|
|`PINCH` (`cameraGesturePinch`)|Pinch gesture, typically assigned to the zoom control.|`zoom` `exposureCorrection` `none`| |
||||||
|
|`TAP` (`cameraGestureTap`)|Single tap gesture, typically assigned to the focus control.|`focus` `focusWithMarker` `capture` `none`| |
||||||
|
|`LONG_TAP` (`cameraGestureLongTap`)|Long tap gesture.|`focus` `focusWithMarker` `capture` `none`| |
||||||
|
|`SCROLL_HORIZONTAL` (`cameraGestureScrollHorizontal`)|Horizontal movement gesture.|`zoom` `exposureCorrection` `none`| |
||||||
|
|`SCROLL_VERTICAL` (`cameraGestureScrollVertical`)|Vertical movement gesture.|`zoom` `exposureCorrection` `none`| |
||||||
|
|
||||||
|
|
||||||
|
## Sizing Behavior |
||||||
|
|
||||||
|
### Preview Size |
||||||
|
|
||||||
|
`CameraView` has a smart measuring behavior that will let you do what you want with a few flags. |
||||||
|
Measuring is controlled simply by `layout_width` and `layout_height` attributes, with this meaning: |
||||||
|
|
||||||
|
- `WRAP_CONTENT` : try to stretch this dimension to respect the preview aspect ratio. |
||||||
|
- `MATCH_PARENT` : fill this dimension, even if this means ignoring the aspect ratio. |
||||||
|
- Fixed values (e.g. `500dp`) : respect this dimension. |
||||||
|
|
||||||
|
You can have previews of all sizes, not just the supported presets. Whaterever you do, |
||||||
|
the preview will never be distorted. |
||||||
|
|
||||||
|
#### Center Inside |
||||||
|
|
||||||
|
You can emulate a **center inside** behavior (like the `ImageView` scaletype) by setting |
||||||
|
both dimensions to `wrap_content`. The camera will get the biggest possible size that fits |
||||||
|
into your bounds, just like what happens with image views. |
||||||
|
|
||||||
Please read the [official website](https://natario1.github.io/CameraView) for setup instructions and documentation. |
|
||||||
You might also be interested in our [changelog](https://natario1.github.io/CameraView/about/changelog) |
|
||||||
or in the [v1 migration guide](https://natario1.github.io/CameraView/extra/v1-migration-guide). |
|
||||||
Using CameraView is extremely simple: |
|
||||||
|
|
||||||
```xml |
```xml |
||||||
<com.otaliastudios.cameraview.CameraView |
<com.otaliastudios.cameraview.CameraView |
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|
||||||
android:layout_width="wrap_content" |
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" /> |
||||||
|
``` |
||||||
|
|
||||||
|
This means that the whole preview is visible, and the image output matches what was visible |
||||||
|
during the capture. |
||||||
|
|
||||||
|
#### Center Crop |
||||||
|
|
||||||
|
You can emulate a **center crop** behavior by setting both dimensions to fixed values or to |
||||||
|
`MATCH_PARENT`. The camera view will fill the rect. If your dimensions don't match the aspect ratio |
||||||
|
of the internal preview surface, the surface will be cropped to fill the view, |
||||||
|
just like `android:scaleType="centerCrop"` on an `ImageView`. |
||||||
|
|
||||||
|
```xml |
||||||
|
<com.otaliastudios.cameraview.CameraView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
``` |
||||||
|
|
||||||
|
This means that part of the preview is hidden, and the image output will contain parts of the scene |
||||||
|
that were not visible during the capture. If this is a problem, see [cameraCropOutput](#cameracropoutput). |
||||||
|
|
||||||
|
### Picture Size |
||||||
|
|
||||||
|
On top of this, you can control the actual size of the output picture, among the list of available sizes. |
||||||
|
It is the size of the final JPEG picture. This can be achieved directly through XML, or |
||||||
|
using the `SizeSelector` class: |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setPictureSize(new SizeSelector() { |
||||||
|
@Override |
||||||
|
public List<Size> select(List<Size> source) { |
||||||
|
// Receives a list of available sizes. |
||||||
|
// Must return a list of acceptable sizes. |
||||||
|
} |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
In practice, this is way easier using XML attributes or leveraging the `SizeSelectors` utilities: |
||||||
|
|
||||||
|
|Constraint|XML attr|SizeSelector| |
||||||
|
|----------|--------|------------| |
||||||
|
|min. width|`app:cameraPictureSizeMinWidth="100"`|`SizeSelectors.minWidth(100)`| |
||||||
|
|min. height|`app:cameraPictureSizeMinHeight="100"`|`SizeSelectors.minHeight(100)`| |
||||||
|
|max. width|`app:cameraPictureSizeMaxWidth="3000"`|`SizeSelectors.maxWidth(3000)`| |
||||||
|
|max. height|`app:cameraPictureSizeMaxHeight="3000"`|`SizeSelectors.maxHeight(3000)`| |
||||||
|
|min. area|`app:cameraPictureSizeMinArea="1000000"`|`SizeSelectors.minArea(1000000)`| |
||||||
|
|max. area|`app:cameraPictureSizeMaxArea="5000000"`|`SizeSelectors.maxArea(5000000)`| |
||||||
|
|aspect ratio|`app:cameraPictureSizeAspectRatio="1:1"`|`SizeSelectors.aspectRatio(AspectRatio.of(1,1), 0)`| |
||||||
|
|smallest|`app:cameraPictureSizeSmallest="true"`|`SizeSelectors.smallest()`| |
||||||
|
|biggest (**default**)|`app:cameraPictureSizeBiggest="true"`|`SizeSelectors.biggest()`| |
||||||
|
|
||||||
|
If you declare more than one XML constraint, the resulting selector will try to match **all** the |
||||||
|
constraints. Be careful - it is very likely that applying lots of constraints will give |
||||||
|
empty results. |
||||||
|
|
||||||
|
#### SizeSelectors utilities |
||||||
|
|
||||||
|
For more versatility, or to address selection issues with multiple constraints, |
||||||
|
we encourage you to use `SizeSelectors` utilities, that will let you merge different selectors. |
||||||
|
|
||||||
|
This selector will try to find square sizes bigger than 1000x2000. If none is found, it falls back |
||||||
|
to just square sizes: |
||||||
|
|
||||||
|
```java |
||||||
|
SizeSelector width = SizeSelectors.minWidth(1000); |
||||||
|
SizeSelector height = SizeSelectors.minHeight(2000); |
||||||
|
SizeSelector dimensions = SizeSelectors.and(width, height); // Matches sizes bigger than 1000x2000. |
||||||
|
SizeSelector ratio = SizeSelectors.aspectRatio(AspectRatio.of(1, 1), 0); // Matches 1:1 sizes. |
||||||
|
|
||||||
|
SizeSelector result = SizeSelectors.or( |
||||||
|
SizeSelectors.and(ratio, dimensions), // Try to match both constraints |
||||||
|
ratio, // If none is found, at least try to match the aspect ratio |
||||||
|
SizeSelectors.biggest() // If none is found, take the biggest |
||||||
|
); |
||||||
|
camera.setPictureSize(result); |
||||||
|
``` |
||||||
|
|
||||||
|
## Camera controls |
||||||
|
|
||||||
|
Most camera parameters can be controlled through XML attributes or linked methods. |
||||||
|
|
||||||
|
```xml |
||||||
|
<com.otaliastudios.cameraview.CameraView |
||||||
|
android:id="@+id/camera" |
||||||
|
android:layout_width="match_parent" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
app:cameraPictureSizeMinWidth="@integer/picture_min_width" |
android:keepScreenOn="true" |
||||||
app:cameraPictureSizeMinHeight="@integer/picture_min_height" |
app:cameraFacing="back" |
||||||
app:cameraPictureSizeMaxWidth="@integer/picture_max_width" |
app:cameraFlash="off" |
||||||
app:cameraPictureSizeMaxHeight="@integer/picture_max_height" |
app:cameraGrid="off" |
||||||
app:cameraPictureSizeMinArea="@integer/picture_min_area" |
app:cameraSessionType="picture" |
||||||
app:cameraPictureSizeMaxArea="@integer/picture_max_area" |
app:cameraCropOutput="false" |
||||||
app:cameraPictureSizeSmallest="false|true" |
app:cameraJpegQuality="100" |
||||||
app:cameraPictureSizeBiggest="false|true" |
app:cameraVideoQuality="max480p" |
||||||
app:cameraPictureSizeAspectRatio="@string/video_ratio" |
app:cameraVideoCodec="deviceDefault" |
||||||
app:cameraVideoSizeMinWidth="@integer/video_min_width" |
app:cameraWhiteBalance="auto" |
||||||
app:cameraVideoSizeMinHeight="@integer/video_min_height" |
app:cameraHdr="off" |
||||||
app:cameraVideoSizeMaxWidth="@integer/video_max_width" |
app:cameraAudio="on" |
||||||
app:cameraVideoSizeMaxHeight="@integer/video_max_height" |
app:cameraPlaySounds="true" |
||||||
app:cameraVideoSizeMinArea="@integer/video_min_area" |
app:cameraVideoMaxSize="0" |
||||||
app:cameraVideoSizeMaxArea="@integer/video_max_area" |
app:cameraVideoMaxDuration="0"/> |
||||||
app:cameraVideoSizeSmallest="false|true" |
|
||||||
app:cameraVideoSizeBiggest="false|true" |
|
||||||
app:cameraVideoSizeAspectRatio="@string/video_ratio" |
|
||||||
app:cameraSnapshotMaxWidth="@integer/snapshot_max_width" |
|
||||||
app:cameraSnapshotMaxHeight="@integer/snapshot_max_height" |
|
||||||
app:cameraFrameProcessingMaxWidth="@integer/processing_max_width" |
|
||||||
app:cameraFrameProcessingMaxHeight="@integer/processing_max_height" |
|
||||||
app:cameraFrameProcessingFormat="@integer/processing_format" |
|
||||||
app:cameraFrameProcessingPoolSize="@integer/processing_pool_size" |
|
||||||
app:cameraFrameProcessingExecutors="@integer/processing_executors" |
|
||||||
app:cameraVideoBitRate="@integer/video_bit_rate" |
|
||||||
app:cameraAudioBitRate="@integer/audio_bit_rate" |
|
||||||
app:cameraGestureTap="none|autoFocus|takePicture" |
|
||||||
app:cameraGestureLongTap="none|autoFocus|takePicture" |
|
||||||
app:cameraGesturePinch="none|zoom|exposureCorrection|filterControl1|filterControl2" |
|
||||||
app:cameraGestureScrollHorizontal="none|zoom|exposureCorrection|filterControl1|filterControl2" |
|
||||||
app:cameraGestureScrollVertical="none|zoom|exposureCorrection|filterControl1|filterControl2" |
|
||||||
app:cameraEngine="camera1|camera2" |
|
||||||
app:cameraPreview="glSurface|surface|texture" |
|
||||||
app:cameraPreviewFrameRate="@integer/preview_frame_rate" |
|
||||||
app:cameraPreviewFrameRateExact="false|true" |
|
||||||
app:cameraFacing="back|front" |
|
||||||
app:cameraHdr="on|off" |
|
||||||
app:cameraFlash="on|auto|torch|off" |
|
||||||
app:cameraWhiteBalance="auto|cloudy|daylight|fluorescent|incandescent" |
|
||||||
app:cameraMode="picture|video" |
|
||||||
app:cameraAudio="on|off|mono|stereo" |
|
||||||
app:cameraGrid="draw3x3|draw4x4|drawPhi|off" |
|
||||||
app:cameraGridColor="@color/grid_color" |
|
||||||
app:cameraPlaySounds="true|false" |
|
||||||
app:cameraVideoMaxSize="@integer/video_max_size" |
|
||||||
app:cameraVideoMaxDuration="@integer/video_max_duration" |
|
||||||
app:cameraVideoCodec="deviceDefault|h264|h263" |
|
||||||
app:cameraAutoFocusResetDelay="@integer/autofocus_delay" |
|
||||||
app: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|dng" |
|
||||||
app:cameraRequestPermissions="true|false" |
|
||||||
app:cameraExperimental="false|true"> |
|
||||||
|
|
||||||
<!-- Watermark! --> |
|
||||||
<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" |
|
||||||
app:layout_drawOnPictureSnapshot="true|false" |
|
||||||
app:layout_drawOnVideoSnapshot="true|false"/> |
|
||||||
|
|
||||||
</com.otaliastudios.cameraview.CameraView> |
|
||||||
``` |
``` |
||||||
|
|
||||||
|
|XML Attribute|Method|Values|Default Value| |
||||||
|
|-------------|------|------|-------------| |
||||||
|
|[`cameraSessionType`](#camerasessiontype)|`setSessionType()`|`picture` `video`|`picture`| |
||||||
|
|[`cameraFacing`](#camerafacing)|`setFacing()`|`back` `front`|`back`| |
||||||
|
|[`cameraFlash`](#cameraflash)|`setFlash()`|`off` `on` `auto` `torch`|`off`| |
||||||
|
|[`cameraGrid`](#cameragrid)|`setGrid()`|`off` `draw3x3` `draw4x4` `drawPhi`|`off`| |
||||||
|
|[`cameraCropOutput`](#cameracropoutput)|`setCropOutput()`|`true` `false`|`false`| |
||||||
|
|[`cameraJpegQuality`](#camerajpegquality)|`setJpegQuality()`|`0 < n <= 100`|`100`| |
||||||
|
|[`cameraVideoQuality`](#cameravideoquality)|`setVideoQuality()`|`lowest` `highest` `maxQvga` `max480p` `max720p` `max1080p` `max2160p`|`max480p`| |
||||||
|
|[`cameraVideoCodec`](#cameravideocodec)|`setVideoCodec()`|`deviceDefault` `h263` `h264`|`deviceDefault`| |
||||||
|
|[`cameraWhiteBalance`](#camerawhitebalance)|`setWhiteBalance()`|`auto` `incandescent` `fluorescent` `daylight` `cloudy`|`auto`| |
||||||
|
|[`cameraHdr`](#camerahdr)|`setHdr()`|`off` `on`|`off`| |
||||||
|
|[`cameraAudio`](#cameraaudio)|`setAudio()`|`off` `on`|`on`| |
||||||
|
|[`cameraPlaySounds`](#cameraplaysounds)|`setPlaySounds()`|`true` `false`|`true`| |
||||||
|
|[`cameraVideoMaxSize`](#cameravideomaxsize)|`setVideoMaxSize()`|number|`0`| |
||||||
|
|[`cameraVideoMaxDuration`](#cameravideomaxduration)|`setVideoMaxDuration()`|number|`0`| |
||||||
|
|
||||||
|
#### cameraSessionType |
||||||
|
|
||||||
|
What to capture - either picture or video. This has a couple of consequences: |
||||||
|
|
||||||
|
- Sizing: picture and preview size are chosen among the available picture or video sizes, |
||||||
|
depending on the flag. The picture size is chosen according to the given [size selector](#picture-size). |
||||||
|
When `video`, in addition, we try to match the `videoQuality` aspect ratio. |
||||||
|
- Picture capturing: due to sizing behavior, capturing pictures in `video` mode might lead to |
||||||
|
inconsistent results. In this case it is encouraged to use `captureSnapshot` instead, which will |
||||||
|
capture preview frames. This is fast and thus works well with slower camera sensors. |
||||||
|
- Picture capturing: while recording a video, image capturing might work, but it is not guaranteed |
||||||
|
(it's device dependent) |
||||||
|
- Permission behavior: when requesting a `video` session, the record audio permission will be requested. |
||||||
|
If this is needed, the audio permission should be added to your manifest or the app will crash. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setSessionType(SessionType.PICTURE); |
||||||
|
cameraView.setSessionType(SessionType.VIDEO); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraFacing |
||||||
|
|
||||||
|
Which camera to use, either back facing or front facing. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setFacing(Facing.BACK); |
||||||
|
cameraView.setFacing(Facing.FRONT); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraFlash |
||||||
|
|
||||||
|
Flash mode, either off, on, auto or *torch*. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setFlash(Flash.OFF); |
||||||
|
cameraView.setFlash(Flash.ON); |
||||||
|
cameraView.setFlash(Flash.AUTO); |
||||||
|
cameraView.setFlash(Flash.TORCH); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraGrid |
||||||
|
|
||||||
|
Lets you draw grids over the camera preview. Supported values are `off`, `draw3x3` and `draw4x4` |
||||||
|
for regular grids, and `drawPhi` for a grid based on the golden ratio constant, often used in photography. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setGrid(Grid.OFF); |
||||||
|
cameraView.setGrid(Grid.DRAW_3X3); |
||||||
|
cameraView.setGrid(Grid.DRAW_4X4); |
||||||
|
cameraView.setGrid(Grid.DRAW_PHI); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraCropOutput |
||||||
|
|
||||||
|
Whether the output picture should be cropped to fit the aspect ratio of the preview surface. |
||||||
|
This can guarantee consistency between what the user sees and the final output, if you fixed |
||||||
|
the camera view dimensions. This does not support videos. |
||||||
|
|
||||||
|
#### cameraJpegQuality |
||||||
|
|
||||||
|
Sets the JPEG quality of pictures. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setJpegQuality(100); |
||||||
|
cameraView.setJpegQuality(50); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraVideoQuality |
||||||
|
|
||||||
|
Sets the desired video quality. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setVideoQuality(VideoQuality.LOWEST); |
||||||
|
cameraView.setVideoQuality(VideoQuality.HIGHEST); |
||||||
|
cameraView.setVideoQuality(VideoQuality.MAX_QVGA); |
||||||
|
cameraView.setVideoQuality(VideoQuality.MAX_480P); |
||||||
|
cameraView.setVideoQuality(VideoQuality.MAX_720P); |
||||||
|
cameraView.setVideoQuality(VideoQuality.MAX_1080P); |
||||||
|
cameraView.setVideoQuality(VideoQuality.MAX_2160P); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraVideoCodec |
||||||
|
|
||||||
|
Sets the encoder for video recordings. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setVideoCodec(VideoCodec.DEVICE_DEFAULT); |
||||||
|
cameraView.setVideoCodec(VideoCodec.H_263); |
||||||
|
cameraView.setVideoCodec(VideoCodec.H_264); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraWhiteBalance |
||||||
|
|
||||||
|
Sets the desired white balance for the current session. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setWhiteBalance(WhiteBalance.AUTO); |
||||||
|
cameraView.setWhiteBalance(WhiteBalance.INCANDESCENT); |
||||||
|
cameraView.setWhiteBalance(WhiteBalance.FLUORESCENT); |
||||||
|
cameraView.setWhiteBalance(WhiteBalance.DAYLIGHT); |
||||||
|
cameraView.setWhiteBalance(WhiteBalance.CLOUDY); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraHdr |
||||||
|
|
||||||
|
Turns on or off HDR captures. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setHdr(Hdr.OFF); |
||||||
|
cameraView.setHdr(Hdr.ON); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraAudio |
||||||
|
|
||||||
|
Turns on or off audio stream while recording videos. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setAudio(Audio.OFF); |
||||||
|
cameraView.setAudio(Audio.ON); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraPlaySounds |
||||||
|
|
||||||
|
Controls whether we should play platform-provided sounds during certain events |
||||||
|
(shutter click, focus completed). Please note that: |
||||||
|
|
||||||
|
- on API < 16, this flag is always set to `false` |
||||||
|
- the Camera1 engine will always play shutter sounds regardless of this flag |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setPlaySounds(true); |
||||||
|
cameraView.setPlaySounds(false); |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraVideoMaxSize |
||||||
|
|
||||||
|
Defines the maximum size in bytes for recorded video files. |
||||||
|
Once this size is reached, the recording will automatically stop. |
||||||
|
Defaults to unlimited size. Use 0 or negatives to disable. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setVideoMaxSize(100000); |
||||||
|
cameraView.setVideoMaxSize(0); // Disable |
||||||
|
``` |
||||||
|
|
||||||
|
#### cameraVideoMaxDuration |
||||||
|
|
||||||
|
Defines the maximum duration in milliseconds for video recordings. |
||||||
|
Once this duration is reached, the recording will automatically stop. |
||||||
|
Defaults to unlimited duration. Use 0 or negatives to disable. |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.setVideoMaxDuration(100000); |
||||||
|
cameraView.setVideoMaxDuration(0); // Disable |
||||||
|
``` |
||||||
|
|
||||||
|
## Frame Processing |
||||||
|
|
||||||
|
We support frame processors that will receive data from the camera preview stream: |
||||||
|
|
||||||
|
```java |
||||||
|
cameraView.addFrameProcessor(new FrameProcessor() { |
||||||
|
@Override |
||||||
|
@WorkerThread |
||||||
|
public void process(Frame frame) { |
||||||
|
byte[] data = frame.getData(); |
||||||
|
int rotation = frame.getRotation(); |
||||||
|
long time = frame.getTime(); |
||||||
|
Size size = frame.getSize(); |
||||||
|
int format = frame.getFormat(); |
||||||
|
// Process... |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
For your convenience, the `FrameProcessor` method is run in a background thread so you can do your job |
||||||
|
in a synchronous fashion. Once the process method returns, internally we will re-use the `Frame` instance and |
||||||
|
apply new data to it. So: |
||||||
|
|
||||||
|
- you can do your job synchronously in the `process()` method |
||||||
|
- if you must hold the `Frame` instance longer, use `frame = frame.freeze()` to get a frozen instance |
||||||
|
that will not be affected |
||||||
|
|
||||||
|
|Frame API|Type|Description| |
||||||
|
|---------|----|-----------| |
||||||
|
|`frame.getData()`|`byte[]`|The current preview frame, in its original orientation.| |
||||||
|
|`frame.getTime()`|`long`|The preview timestamp, in `System.currentTimeMillis()` reference.| |
||||||
|
|`frame.getRotation()`|`int`|The rotation that should be applied to the byte array in order to see what the user sees.| |
||||||
|
|`frame.getSize()`|`Size`|The frame size, before any rotation is applied, to access data.| |
||||||
|
|`frame.getFormat()`|`int`|The frame `ImageFormat`. This will always be `ImageFormat.NV21` for now.| |
||||||
|
|`frame.freeze()`|`Frame`|Clones this frame and makes it immutable. Can be expensive because requires copying the byte array.| |
||||||
|
|`frame.release()`|`-`|Disposes the content of this frame. Should be used on frozen frames to release memory.| |
||||||
|
|
||||||
|
## Other APIs |
||||||
|
|
||||||
|
Other APIs not mentioned above are provided, and are well documented and commented in code. |
||||||
|
|
||||||
|
|Method|Description| |
||||||
|
|------|-----------| |
||||||
|
|`isStarted()`|Returns true if `start()` was called succesfully. This does not mean that camera is open or showing preview.| |
||||||
|
|`mapGesture(Gesture, GestureAction)`|Maps a certain gesture to a certain action. No-op if the action is not supported.| |
||||||
|
|`getGestureAction(Gesture)`|Returns the action currently mapped to the given gesture.| |
||||||
|
|`clearGesture(Gesture)`|Clears any action mapped to the given gesture.| |
||||||
|
|`getCameraOptions()`|If camera was started, returns non-null object with information about what is supported.| |
||||||
|
|`getExtraProperties()`|If camera was started, returns non-null object with extra information about the camera sensor. Not very useful at the moment.| |
||||||
|
|`setZoom(float)`, `getZoom()`|Sets a zoom value, where 0 means camera zoomed out and 1 means zoomed in. No-op if zoom is not supported, or camera not started.| |
||||||
|
|`setExposureCorrection(float)`, `getExposureCorrection()`|Sets exposure compensation EV value, in camera stops. No-op if this is not supported. Should be between the bounds returned by CameraOptions.| |
||||||
|
|`toggleFacing()`|Toggles the facing value between `Facing.FRONT` and `Facing.BACK`.| |
||||||
|
|`setLocation(Location)`|Sets location data to be appended to picture/video metadata.| |
||||||
|
|`setLocation(double, double)`|Sets latitude and longitude to be appended to picture/video metadata.| |
||||||
|
|`getLocation()`|Retrieves location data previously applied with setLocation().| |
||||||
|
|`startAutoFocus(float, float)`|Starts an autofocus process at the given coordinates, with respect to the view dimensions.| |
||||||
|
|`getPreviewSize()`|Returns the size of the preview surface. If CameraView was not constrained in its layout phase (e.g. it was `wrap_content`), this will return the same aspect ratio of CameraView.| |
||||||
|
|`getSnapshotSize()`|Returns `getPreviewSize()`, since a snapshot is a preview frame.| |
||||||
|
|`getPictureSize()`|Returns the size of the output picture. The aspect ratio is consistent with `getPreviewSize()`.| |
||||||
|
|
||||||
|
Take also a look at public methods in `CameraUtils`, `CameraOptions`, `ExtraProperties`. |
||||||
|
|
||||||
|
## Permissions behavior |
||||||
|
|
||||||
|
`CameraView` needs two permissions: |
||||||
|
|
||||||
|
- `android.permission.CAMERA` : required for capturing pictures and videos |
||||||
|
- `android.permission.RECORD_AUDIO` : required for capturing videos with `Audio.ON` (the default) |
||||||
|
|
||||||
|
### Declaration |
||||||
|
|
||||||
|
The library manifest file declares the `android.permission.CAMERA` permission, but not the audio one. |
||||||
|
This means that: |
||||||
|
|
||||||
|
- If you wish to record videos with `Audio.ON` (the default), you should also add |
||||||
|
`android.permission.RECORD_AUDIO` to required permissions |
||||||
|
|
||||||
|
```xml |
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/> |
||||||
|
``` |
||||||
|
|
||||||
|
- If you want your app to be installed only on devices that have a camera, you should add: |
||||||
|
|
||||||
|
```xml |
||||||
|
<uses-feature |
||||||
|
android:name="android.hardware.camera" |
||||||
|
android:required="true"/> |
||||||
|
``` |
||||||
|
|
||||||
|
If you don't request this feature, you can use `CameraUtils.hasCameras()` to detect if current |
||||||
|
device has cameras, and then start the camera view. |
||||||
|
|
||||||
|
### Handling |
||||||
|
|
||||||
|
On Marshmallow+, the user must explicitly approve our permissions. You can |
||||||
|
|
||||||
|
- handle permissions yourself and then call `cameraView.start()` once they are acquired |
||||||
|
- or call `cameraView.start()` anyway: `CameraView` will present a permission request to the user based on |
||||||
|
whether they are needed or not with the current configuration. |
||||||
|
|
||||||
|
In the second case, you should restart the camera if you have a successful response from `onRequestPermissionResults()`. |
||||||
|
|
||||||
|
## Logging |
||||||
|
|
||||||
|
`CameraView` will log a lot of interesting events related to the camera lifecycle. These are important |
||||||
|
to identify bugs. The default logger will simply use Android `Log` methods posting to logcat. |
||||||
|
|
||||||
|
You can attach and detach external loggers using `CameraLogger.registerLogger()`: |
||||||
|
|
||||||
|
```java |
||||||
|
CameraLogger.registerLogger(new Logger() { |
||||||
|
@Override |
||||||
|
public void log(@LogLevel int level, String tag, String message, @Nullable Throwable throwable) { |
||||||
|
// For example... |
||||||
|
Crashlytics.log(message); |
||||||
|
} |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
Make sure you enable the logger using `CameraLogger.setLogLevel(@LogLevel int)`. The default will only |
||||||
|
log error events. |
||||||
|
|
||||||
|
## Device-specific issues |
||||||
|
|
||||||
|
There are a couple of known issues if you are working with certain devices. The emulator is one of |
||||||
|
the most tricky in this sense. |
||||||
|
|
||||||
|
- Devices, or activities, with hardware acceleration turned off: this can be the case with emulators. |
||||||
|
In this case we will use SurfaceView as our surface provider. That is intrinsically flawed and can't |
||||||
|
deal with all we want to do here (runtime layout changes, scaling, etc.). So, nothing to do in this case. |
||||||
|
- Devices with no support for MediaRecorder: the emulator does not support it, officially. This means |
||||||
|
that video/audio recording is flawed. Again, not our fault. |
||||||
|
|
||||||
|
## Roadmap |
||||||
|
|
||||||
|
This is what was done since the library was forked. I have kept the original structure, but practically |
||||||
|
all the code was changed. |
||||||
|
|
||||||
|
- *a huge number of serious bugs fixed* |
||||||
|
- *decent orientation support for both pictures and videos* |
||||||
|
- *less dependencies* |
||||||
|
- *EXIF support* |
||||||
|
- *real tap-to-focus support* |
||||||
|
- *pinch-to-zoom support* |
||||||
|
- *simpler APIs, docs and heavily commented code* |
||||||
|
- *new `captureSnapshot` API* |
||||||
|
- *new `setLocation` and `setWhiteBalance` APIs* |
||||||
|
- *new `setGrid` APIs, to draw 3x3, 4x4 or golden ratio grids* |
||||||
|
- *option to pass a `File` when recording a video* |
||||||
|
- *other minor API additions* |
||||||
|
- *replacing Method and Permissions stuff with simpler `sessionType`* |
||||||
|
- *smart measuring and sizing behavior, replacing bugged `adjustViewBounds`* |
||||||
|
- *measure `CameraView` as center crop or center inside* |
||||||
|
- *add multiple `CameraListener`s for events* |
||||||
|
- *gesture framework support, map gestures to camera controls* |
||||||
|
- *pinch gesture support* |
||||||
|
- *tap & long tap gesture support* |
||||||
|
- *scroll gestures support* |
||||||
|
- *MediaActionSound support* |
||||||
|
- *Hdr controls* |
||||||
|
- *zoom and exposure correction controls* |
||||||
|
- *Tests!* |
||||||
|
- *`CameraLogger` APIs for logging and bug reports* |
||||||
|
- *Better threading, start() in worker thread and callbacks in UI* |
||||||
|
- *Frame processor support* |
||||||
|
- *inject external loggers* |
||||||
|
- *error handling* |
||||||
|
- *capture size selectors* |
||||||
|
|
||||||
|
These are still things that need to be done, off the top of my head: |
||||||
|
|
||||||
|
- [ ] `Camera2` integration |
||||||
|
- [ ] animate grid lines similar to stock camera app |
||||||
|
- [ ] add onRequestPermissionResults for easy permission callback |
||||||
|
- [ ] decent code coverage |
||||||
|
|
||||||
|
# Contributing and licenses |
||||||
|
|
||||||
|
The original project which served as a starting point for this library, |
||||||
|
[CameraKit-Android](https://github.com/wonderkiln/CameraKit-Android), is licensed under the |
||||||
|
[MIT](https://github.com/wonderkiln/CameraKit-Android/blob/master/LICENSE) license. |
||||||
|
Additional work is now licensed under the [MIT](https://github.com/natario1/CameraView/blob/master/LICENSE) |
||||||
|
license as well. |
||||||
|
|
||||||
|
You are welcome to contribute with suggestions or pull requests, this is under active development. |
||||||
|
To contact me, <a href="mailto:mat.iavarone@gmail.com">send an email.</a> |
||||||
|
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 46 KiB |
@ -0,0 +1,35 @@ |
|||||||
|
// 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 |
||||||
|
} |
@ -1,30 +0,0 @@ |
|||||||
|
|
||||||
buildscript { |
|
||||||
|
|
||||||
extra["minSdkVersion"] = 15 |
|
||||||
extra["compileSdkVersion"] = 31 |
|
||||||
extra["targetSdkVersion"] = 31 |
|
||||||
|
|
||||||
repositories { |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
classpath("com.android.tools.build:gradle:7.0.3") |
|
||||||
classpath("io.deepmedia.tools:publisher:0.6.0") |
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
allprojects { |
|
||||||
repositories { |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.register("clean", Delete::class) { |
|
||||||
delete(buildDir) |
|
||||||
} |
|
@ -0,0 +1,217 @@ |
|||||||
|
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 |
||||||
|
|
@ -1,137 +0,0 @@ |
|||||||
import io.deepmedia.tools.publisher.common.License |
|
||||||
import io.deepmedia.tools.publisher.common.Release |
|
||||||
import io.deepmedia.tools.publisher.common.GithubScm |
|
||||||
|
|
||||||
plugins { |
|
||||||
id("com.android.library") |
|
||||||
id("kotlin-android") |
|
||||||
id("io.deepmedia.tools.publisher") |
|
||||||
id("jacoco") |
|
||||||
} |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdk = property("compileSdkVersion") as Int |
|
||||||
defaultConfig { |
|
||||||
minSdk = property("minSdkVersion") as Int |
|
||||||
targetSdk = property("targetSdkVersion") as Int |
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
|
||||||
testInstrumentationRunnerArguments["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.1") |
|
||||||
testImplementation("org.mockito:mockito-inline:2.28.2") |
|
||||||
|
|
||||||
androidTestImplementation("androidx.test:runner:1.4.0") |
|
||||||
androidTestImplementation("androidx.test:rules:1.4.0") |
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.3") |
|
||||||
androidTestImplementation("org.mockito:mockito-android:2.28.2") |
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") |
|
||||||
|
|
||||||
api("androidx.exifinterface:exifinterface:1.3.3") |
|
||||||
api("androidx.lifecycle:lifecycle-common:2.3.1") |
|
||||||
api("com.google.android.gms:play-services-tasks:17.2.1") |
|
||||||
implementation("androidx.annotation:annotation:1.2.0") |
|
||||||
implementation("com.otaliastudios.opengl:egloo:0.6.1") |
|
||||||
} |
|
||||||
|
|
||||||
// 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.scm = GithubScm("natario1", "CameraView") |
|
||||||
project.addLicense(License.APACHE_2_0) |
|
||||||
project.addDeveloper("natario1", "mat.iavarone@gmail.com") |
|
||||||
release.sources = Release.SOURCES_AUTO |
|
||||||
release.docs = Release.DOCS_AUTO |
|
||||||
release.version = "2.7.2" |
|
||||||
|
|
||||||
directory() |
|
||||||
|
|
||||||
sonatype { |
|
||||||
auth.user = "SONATYPE_USER" |
|
||||||
auth.password = "SONATYPE_PASSWORD" |
|
||||||
signing.key = "SIGNING_KEY" |
|
||||||
signing.password = "SIGNING_PASSWORD" |
|
||||||
} |
|
||||||
|
|
||||||
sonatype("snapshot") { |
|
||||||
repository = io.deepmedia.tools.publisher.sonatype.Sonatype.OSSRH_SNAPSHOT_1 |
|
||||||
release.version = "latest-SNAPSHOT" |
|
||||||
auth.user = "SONATYPE_USER" |
|
||||||
auth.password = "SONATYPE_PASSWORD" |
|
||||||
signing.key = "SIGNING_KEY" |
|
||||||
signing.password = "SIGNING_PASSWORD" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 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/outputs/unit_test_code_coverage/debugUnitTest/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.required.set(true) |
|
||||||
reports.xml.required.set(true) |
|
||||||
reports.html.outputLocation.set(file("$coverageOutputDir/html")) |
|
||||||
reports.xml.outputLocation.set(file("$coverageOutputDir/xml/report.xml")) |
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
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
@ -0,0 +1,76 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
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()))); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,644 @@ |
|||||||
|
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
|
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
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)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
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) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -1,55 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,66 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -1,146 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,39 @@ |
|||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -1,51 +0,0 @@ |
|||||||
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();
|
|
||||||
} |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
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
@ -1,215 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
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)); |
|
||||||
} |
|
||||||
} |
|
@ -1,101 +0,0 @@ |
|||||||
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)); |
|
||||||
} |
|
||||||
} |
|
@ -1,172 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,212 +0,0 @@ |
|||||||
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)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
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()); |
|
||||||
} |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
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()); |
|
||||||
} |
|
||||||
} |
|
@ -1,112 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,73 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,71 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,98 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,85 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,61 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,53 +0,0 @@ |
|||||||
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()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,219 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,109 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,53 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,242 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,104 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,128 +0,0 @@ |
|||||||
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) |
|
||||||
@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()); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,79 +0,0 @@ |
|||||||
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) { } |
|
||||||
} |
|
||||||
} |
|
@ -1,137 +0,0 @@ |
|||||||
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)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,78 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,177 +0,0 @@ |
|||||||
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 com.otaliastudios.cameraview.tools.SdkExclude; |
|
||||||
|
|
||||||
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)); |
|
||||||
} |
|
||||||
|
|
||||||
@SdkExclude(minSdkVersion = 31) // spying views does not work properly on 31, should investigate
|
|
||||||
@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); |
|
||||||
} |
|
||||||
|
|
||||||
@SdkExclude(minSdkVersion = 31) // spying views does not work properly on 31, should investigate
|
|
||||||
@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); |
|
||||||
} |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
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()); |
|
||||||
} |
|
||||||
} |
|
@ -1,63 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -1,211 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
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"); |
|
||||||
} |
|
||||||
} |
|
@ -1,149 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
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"; |
|
||||||
} |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
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"; |
|
||||||
} |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
<?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"/> |
|
@ -1,8 +0,0 @@ |
|||||||
<?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"/> |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue