Compare commits
	
		
			1 Commits 
		
	
	
		
			main
			...
			stop-video
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 418f1ed662 | 6 years ago | 
| @ -1 +1 @@ | ||||
| Contributing guidelines are [hosted here](https://natario1.github.io/CameraView/extra/contributing). | ||||
| Contributing guidelines are [hosted here](https://natario1.github.io/CameraView/extra/contributing.html). | ||||
| @ -1,14 +1,10 @@ | ||||
| ### Before you go | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that | ||||
| we can discuss the best approach to address the problem. Without a reference issue and discussion,  | ||||
| unfortunately, this PR will likely be ignored. | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. | ||||
| If the edited files were covered by tests, updated tests are required for merging. Please look into the tests folders and make sure you cover new code. | ||||
| 
 | ||||
| If the edited files were covered by tests, updated tests are required for merging.  | ||||
| Please look into the tests folders and make sure you cover new code. | ||||
| 
 | ||||
| - Fixes: ... (*issue number*) | ||||
| - Fixes ... (*issue number*) | ||||
| - Tests: ... (*yes/no*) | ||||
| - Docs updated: ... (*yes/no*) | ||||
| 
 | ||||
| ### 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,90 @@ | ||||
| # https://github.com/andstatus/andstatus/blob/master/.travis.yml | ||||
| 
 | ||||
| language: android | ||||
| 
 | ||||
| branches: | ||||
|   only: | ||||
|     - master | ||||
|     - /^v\d+\.\d+\.\d+$/ | ||||
|     - /^v\d+\.\d+\.\d+-beta\d+$/ | ||||
|     - /^v\d+\.\d+\.\d+-rc\d+$/ | ||||
| 
 | ||||
| sudo: false | ||||
| 
 | ||||
| jdk: | ||||
|   - oraclejdk8 | ||||
| 
 | ||||
| env: | ||||
|   global: | ||||
|     - EMULATOR_API=22 # 24 has some issues, probably some overlayed window | ||||
|     - EMULATOR_ABI=x86_64 # seems to work with emulator v29. | ||||
|     - EMULATOR_TAG=default # can be google_apis | ||||
|     - PATH=$ANDROID_HOME:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH | ||||
| 
 | ||||
| android: | ||||
|   components: | ||||
|     - tools | ||||
|     - platform-tools | ||||
|     - build-tools-28.0.3 | ||||
|     - 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 | ||||
|   # The channel=4 line looks into canary which brings in v29. | ||||
|   # The previous version v28 was broken: | ||||
|   # https://travis-ci.community/t/android-emulators-not-starting-for-the-last-few-days-late-march-2019/2871/11?u=mikehardy | ||||
|   - export EMULATOR="system-images;android-$EMULATOR_API;$EMULATOR_TAG;$EMULATOR_ABI" | ||||
|   - echo yes | sdkmanager "platforms;android-$EMULATOR_API" # Install sdk for the emulator | ||||
|   - echo yes | sdkmanager --channel=4 "$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 virtual device | ||||
|   - which emulator # ensure we are using the right emulator ($ANDROID_HOME/emulator/emulator) | ||||
|   - emulator -avd test -no-window -no-accel -no-snapshot -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 demo:assembleDebug cameraview:testDebugUnitTest cameraview:connectedCheck cameraview:mergedCoverageReport cameraview:javadoc | ||||
| 
 | ||||
| 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,29 @@ | ||||
| buildscript { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|         google() | ||||
|     } | ||||
| 
 | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:3.4.2' | ||||
|         classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' | ||||
|         classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| allprojects { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|         google() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ext { | ||||
|     compileSdkVersion = 28 | ||||
|     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,262 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply plugin: 'com.github.dcendents.android-maven' | ||||
| apply plugin: 'com.jfrog.bintray' | ||||
| 
 | ||||
| // Required by bintray | ||||
| version = '2.1.0' | ||||
| group = 'com.otaliastudios' | ||||
| 
 | ||||
| //region android dependencies | ||||
| 
 | ||||
| def travis = System.getenv("TRAVIS") | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion rootProject.ext.compileSdkVersion | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion rootProject.ext.minSdkVersion | ||||
|         targetSdkVersion rootProject.ext.targetSdkVersion | ||||
|         versionCode 1 | ||||
|         versionName project.version | ||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
|         if (travis) { | ||||
|             testInstrumentationRunnerArgument "notAnnotation", "com.otaliastudios.cameraview.DoNotRunOnTravis" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     buildTypes { | ||||
|         debug { | ||||
|             testCoverageEnabled true | ||||
|         } | ||||
| 
 | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     testImplementation 'org.mockito:mockito-inline:2.28.2' | ||||
| 
 | ||||
|     androidTestImplementation 'androidx.test:runner:1.2.0' | ||||
|     androidTestImplementation 'androidx.test:rules:1.2.0' | ||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.1' | ||||
|     androidTestImplementation 'org.mockito:mockito-android:2.28.2' | ||||
|     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | ||||
| 
 | ||||
|     api 'androidx.exifinterface:exifinterface:1.0.0' | ||||
|     api 'androidx.lifecycle:lifecycle-common:2.1.0-alpha01' | ||||
|     api 'com.google.android.gms:play-services-tasks:17.0.0' | ||||
|     implementation 'androidx.annotation:annotation:1.1.0' | ||||
| } | ||||
| 
 | ||||
| //endregion | ||||
| 
 | ||||
| //region bintray | ||||
| 
 | ||||
| // install is a task defined by the Gradle Maven plugin, which is used to | ||||
| // publish a maven repo to a local repository. (we actually use the android version of the plugin, | ||||
| // com.github.dcendents.android-maven, to support AARs) | ||||
| // https://docs.gradle.org/current/userguide/maven_plugin.html#sec:maven_tasks | ||||
| install { | ||||
|     // The repositories property is common to all tasks of type Upload and returns the repositories | ||||
|     // into which we will upload data. https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Upload.html#org.gradle.api.tasks.Upload:repositories | ||||
|     // It returns a RepositoryHandler: https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html | ||||
|     repositories { | ||||
|         // The maven plugin adds a mavenInstaller property to the RepositoryHandler which can be used to | ||||
|         // add and configure a local maven repository cache. | ||||
|         // https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html#N11785 | ||||
|         mavenInstaller { | ||||
|             // The object here extends PomFilterContainer so we can configure the pom file here. | ||||
|             // https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/PomFilterContainer.html#pom-groovy.lang.Closure- | ||||
|             pom { | ||||
|                 // Now we are inside a MavenPom object that can be configured. We get the project and configure. | ||||
|                 // https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/MavenPom.html | ||||
|                 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 'Mattia Iavarone' | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| def bintrayUser | ||||
| def bintrayKey | ||||
| 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' | ||||
|     // This excludes our internal folder, which is nice, but also creates | ||||
|     // errors anytime we reference excluded classes in public classes javadocs, | ||||
|     // which is unfortunate. There might be a fix but I don't know any. | ||||
|     // 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" | ||||
| 
 | ||||
|     // Merge unit tests and android tests data | ||||
|     executionData = fileTree(dir: "$buildDir", includes: [ | ||||
|             "jacoco/testDebugUnitTest.exec", // Unit tests | ||||
|             "outputs/code_coverage/debugAndroidTest/connected/*coverage.ec" // Android tests | ||||
|     ]) | ||||
| 
 | ||||
|     // Sources | ||||
|     sourceDirectories = files(android.sourceSets.main.java.sourceFiles) | ||||
|     additionalSourceDirs = files([ // Add BuildConfig and R. | ||||
|             "$buildDir/generated/source/buildConfig/debug", | ||||
|             "$buildDir/generated/source/r/debug" | ||||
|     ]) | ||||
| 
 | ||||
|     // Classes (.class files) | ||||
|     // Not everything in the filter relates to CameraView, | ||||
|     // but let's keep a generic filter | ||||
|     def classDir = "$buildDir/intermediates/javac/debug" | ||||
|     def classFilter = [ | ||||
|             '**/R.class', | ||||
|             '**/R$*.class', | ||||
|             '**/BuildConfig.*', | ||||
|             '**/Manifest*.*', | ||||
|             'android/**', | ||||
|             'androidx/**', | ||||
|             'com/google/**', | ||||
|             '**/*$ViewInjector*.*', | ||||
|             '**/Dagger*Component.class', | ||||
|             '**/Dagger*Component$Builder.class', | ||||
|             '**/*Module_*Factory.class', | ||||
|     ] | ||||
|     if (travis) { | ||||
|         // All these classes are tested by the integration tests that we are not able to | ||||
|         // run on the travis emulator. | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/engine/CameraEngine**.*') | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/engine/Camera1Engine**.*') | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/engine/Camera2Engine**.*') | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/picture/**.*') | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/video/**.*') | ||||
|         // TODO these below could be testable ALSO outside of the integration tests | ||||
|         classFilter.add('**/com/otaliastudios/cameraview/video/encoding/**.*') | ||||
|     } | ||||
|     // We don't test OpenGL filters. | ||||
|     classFilter.add('**/com/otaliastudios/cameraview/filters/**.*') | ||||
| 
 | ||||
|     classDirectories = fileTree(dir: classDir, excludes: classFilter); | ||||
| 
 | ||||
|     reports.html.enabled = true | ||||
|     reports.xml.enabled = true | ||||
|     reports.xml.destination file("$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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| 
 | ||||
| /** | ||||
|  * Thanks to a testInstrumentationRunnerArgument in our build file, we will not | ||||
|  * execute these tests on Travis CI. | ||||
|  * The {@link RetentionPolicy#RUNTIME} is needed! | ||||
|  * | ||||
|  * https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner
 | ||||
|  * | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface DoNotRunOnTravis { | ||||
|     String because() default ""; | ||||
| } | ||||
| @ -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();
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.DoNotRunOnTravis; | ||||
| import com.otaliastudios.cameraview.controls.Engine; | ||||
| 
 | ||||
| import org.junit.Ignore; | ||||
| 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; | ||||
| 
 | ||||
| /** | ||||
|  * 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 | ||||
| @DoNotRunOnTravis(because = "These do work but fail on CI emulators, due to bugs in the Camera1 emulated devices.") | ||||
| public class CameraIntegration1Test extends CameraIntegrationTest { | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Engine getEngine() { | ||||
|         return Engine.CAMERA1; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.DoNotRunOnTravis; | ||||
| import com.otaliastudios.cameraview.controls.Engine; | ||||
| 
 | ||||
| import org.junit.Ignore; | ||||
| 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; | ||||
| 
 | ||||
| /** | ||||
|  * 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 | ||||
| @DoNotRunOnTravis(because = "These do work but fail on CI emulators.") | ||||
| public class CameraIntegration2Test extends CameraIntegrationTest { | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Engine getEngine() { | ||||
|         return Engine.CAMERA2; | ||||
|     } | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -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,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,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,10 +1,9 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| @ -0,0 +1,102 @@ | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Color; | ||||
| import android.graphics.ImageFormat; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.PorterDuff; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.YuvImage; | ||||
| import android.media.Image; | ||||
| import android.media.ImageReader; | ||||
| import android.opengl.GLES20; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.view.Surface; | ||||
| 
 | ||||
| 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.internal.egl.EglCore; | ||||
| import com.otaliastudios.cameraview.internal.egl.EglViewport; | ||||
| import com.otaliastudios.cameraview.internal.egl.EglWindowSurface; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.nio.ByteBuffer; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @SmallTest | ||||
| public class ImageHelperTest extends BaseTest { | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Image getImage() { | ||||
|         ImageReader reader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1); | ||||
|         Surface readerSurface = reader.getSurface(); | ||||
|         final Op<Image> imageOp = new Op<>(true); | ||||
|         reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { | ||||
|             @Override | ||||
|             public void onImageAvailable(ImageReader reader) { | ||||
|                 Image image = reader.acquireLatestImage(); | ||||
|                 if (image != null) imageOp.end(image); | ||||
|             } | ||||
|         }, new Handler(Looper.getMainLooper())); | ||||
| 
 | ||||
|         // Write on reader surface.
 | ||||
|         Canvas readerCanvas = readerSurface.lockCanvas(null); | ||||
|         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); | ||||
|         paint.setColor(Color.RED); | ||||
|         readerCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); | ||||
|         readerCanvas.drawCircle(50, 50, 50, paint); | ||||
|         readerSurface.unlockCanvasAndPost(readerCanvas); | ||||
| 
 | ||||
|         // Wait
 | ||||
|         Image image = imageOp.await(5000); | ||||
|         assertNotNull(image); | ||||
|         return image; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testImage() { | ||||
|         Image image = getImage(); | ||||
|         int width = image.getWidth(); | ||||
|         int height = image.getHeight(); | ||||
|         int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); | ||||
|         int sizeBits = width * height * bitsPerPixel; | ||||
|         int sizeBytes = (int) Math.ceil(sizeBits / 8.0d); | ||||
|         byte[] bytes = new byte[sizeBytes]; | ||||
|         ImageHelper.convertToNV21(image, bytes); | ||||
|         image.close(); | ||||
| 
 | ||||
|         // Read the image
 | ||||
|         YuvImage yuvImage = new YuvImage(bytes, ImageFormat.NV21, width, height, null); | ||||
|         ByteArrayOutputStream jpegStream = new ByteArrayOutputStream(); | ||||
|         yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, jpegStream); | ||||
|         byte[] jpegByteArray = jpegStream.toByteArray(); | ||||
|         Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length); | ||||
|         assertNotNull(bitmap); | ||||
| 
 | ||||
|         // Wanted to do assertions on the color here but it doesn't work. There must be an issue
 | ||||
|         // with how we are drawing the image in this test, since in real camera, the algorithm works well.
 | ||||
|         // So for now let's just test that nothing crashes during this process.
 | ||||
|         // int color = bitmap.getPixel(bitmap.getWidth() - 1, bitmap.getHeight() - 1);
 | ||||
|         // assertEquals(Color.red(color), 255, 5);
 | ||||
|         // assertEquals(Color.green(color), 0, 5);
 | ||||
|         // assertEquals(Color.blue(color), 0, 5);
 | ||||
|         // assertEquals(Color.alpha(color), 0, 5);
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.OrientationEventListener; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 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() { | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertEquals(helper.getDisplayOffset(), -1); | ||||
|         assertEquals(helper.getDeviceOrientation(), -1); | ||||
| 
 | ||||
|         helper.enable(getContext()); | ||||
|         assertNotNull(helper.mListener); | ||||
|         assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
 | ||||
| 
 | ||||
|         // Ensure nothing bad if called twice.
 | ||||
|         helper.enable(getContext()); | ||||
|         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(getContext()); | ||||
|         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,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,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"; | ||||
|     } | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -1,63 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| /** | ||||
|  * Constants for selecting the encoder of audio recordings. | ||||
|  * https://developer.android.com/guide/topics/media/media-formats.html#audio-formats
 | ||||
|  * | ||||
|  * @see CameraView#setAudioCodec(AudioCodec) | ||||
|  */ | ||||
| public enum AudioCodec implements Control { | ||||
| 
 | ||||
|     /** | ||||
|      * Let the device choose its codec. | ||||
|      */ | ||||
|     DEVICE_DEFAULT(0), | ||||
| 
 | ||||
|     /** | ||||
|      * The AAC codec. | ||||
|      */ | ||||
|     AAC(1), | ||||
| 
 | ||||
|     /** | ||||
|      * The HE_AAC codec. | ||||
|      */ | ||||
|     @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) | ||||
|     HE_AAC(2), | ||||
| 
 | ||||
|     /** | ||||
|      * The AAC_ELD codec. | ||||
|      */ | ||||
|     @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) | ||||
|     AAC_ELD(3); | ||||
| 
 | ||||
|     static final AudioCodec DEFAULT = DEVICE_DEFAULT; | ||||
| 
 | ||||
|     private int value; | ||||
| 
 | ||||
|     AudioCodec(int value) { | ||||
|         this.value = value; | ||||
|     } | ||||
| 
 | ||||
|     int value() { | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     static AudioCodec fromValue(int value) { | ||||
|         AudioCodec[] list = AudioCodec.values(); | ||||
|         for (AudioCodec action : list) { | ||||
|             if (action.value() == value) { | ||||
|                 return action; | ||||
|             } | ||||
|         } | ||||
|         return DEFAULT; | ||||
|     } | ||||
| } | ||||
| @ -1,52 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| /** | ||||
|  * Format of the picture results for pictures that are taken with {@link CameraView#takePicture()}. | ||||
|  * This does not apply to picture snapshots. | ||||
|  * | ||||
|  * @see CameraView#setPictureFormat(PictureFormat) | ||||
|  */ | ||||
| public enum PictureFormat implements Control { | ||||
| 
 | ||||
|     /** | ||||
|      * The picture result data will be a JPEG file. | ||||
|      * This value is always supported. | ||||
|      */ | ||||
|     JPEG(0), | ||||
| 
 | ||||
|     /** | ||||
|      * The picture result data will be a DNG file. | ||||
|      * This is only supported with the {@link Engine#CAMERA2} engine and only on | ||||
|      * specific devices. Please check {@link CameraOptions#getSupportedPictureFormats()}. | ||||
|      */ | ||||
|     DNG(1); | ||||
| 
 | ||||
|     static final PictureFormat DEFAULT = JPEG; | ||||
| 
 | ||||
|     private int value; | ||||
| 
 | ||||
|     PictureFormat(int value) { | ||||
|         this.value = value; | ||||
|     } | ||||
| 
 | ||||
|     int value() { | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     static PictureFormat fromValue(int value) { | ||||
|         PictureFormat[] list = PictureFormat.values(); | ||||
|         for (PictureFormat action : list) { | ||||
|             if (action.value() == value) { | ||||
|                 return action; | ||||
|             } | ||||
|         } | ||||
|         return DEFAULT; | ||||
|     } | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -1,978 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import androidx.annotation.CallSuper; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.tasks.Tasks; | ||||
| import com.otaliastudios.cameraview.CameraException; | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.PictureResult; | ||||
| import com.otaliastudios.cameraview.VideoResult; | ||||
| import com.otaliastudios.cameraview.controls.Audio; | ||||
| import com.otaliastudios.cameraview.controls.AudioCodec; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.Mode; | ||||
| import com.otaliastudios.cameraview.controls.PictureFormat; | ||||
| import com.otaliastudios.cameraview.controls.VideoCodec; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| import com.otaliastudios.cameraview.engine.offset.Angles; | ||||
| import com.otaliastudios.cameraview.engine.offset.Reference; | ||||
| import com.otaliastudios.cameraview.engine.orchestrator.CameraState; | ||||
| import com.otaliastudios.cameraview.frame.FrameManager; | ||||
| import com.otaliastudios.cameraview.overlay.Overlay; | ||||
| import com.otaliastudios.cameraview.picture.PictureRecorder; | ||||
| import com.otaliastudios.cameraview.preview.CameraPreview; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| import com.otaliastudios.cameraview.size.SizeSelector; | ||||
| import com.otaliastudios.cameraview.size.SizeSelectors; | ||||
| import com.otaliastudios.cameraview.video.VideoRecorder; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileDescriptor; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Abstract implementation of {@link CameraEngine} that helps in common tasks. | ||||
|  */ | ||||
| public abstract class CameraBaseEngine extends CameraEngine { | ||||
| 
 | ||||
|     protected final static int ALLOWED_ZOOM_OPS = 20; | ||||
|     protected final static int ALLOWED_EV_OPS = 20; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") protected CameraPreview mPreview; | ||||
|     @SuppressWarnings("WeakerAccess") protected CameraOptions mCameraOptions; | ||||
|     @SuppressWarnings("WeakerAccess") protected PictureRecorder mPictureRecorder; | ||||
|     @SuppressWarnings("WeakerAccess") protected VideoRecorder mVideoRecorder; | ||||
|     @SuppressWarnings("WeakerAccess") protected Size mCaptureSize; | ||||
|     @SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize; | ||||
|     @SuppressWarnings("WeakerAccess") protected Size mFrameProcessingSize; | ||||
|     @SuppressWarnings("WeakerAccess") protected int mFrameProcessingFormat; | ||||
|     @SuppressWarnings("WeakerAccess") protected boolean mHasFrameProcessors; | ||||
|     @SuppressWarnings("WeakerAccess") protected Flash mFlash; | ||||
|     @SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance; | ||||
|     @SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec; | ||||
|     @SuppressWarnings("WeakerAccess") protected AudioCodec mAudioCodec; | ||||
|     @SuppressWarnings("WeakerAccess") protected Hdr mHdr; | ||||
|     @SuppressWarnings("WeakerAccess") protected PictureFormat mPictureFormat; | ||||
|     @SuppressWarnings("WeakerAccess") protected Location mLocation; | ||||
|     @SuppressWarnings("WeakerAccess") protected float mZoomValue; | ||||
|     @SuppressWarnings("WeakerAccess") protected float mExposureCorrectionValue; | ||||
|     @SuppressWarnings("WeakerAccess") protected boolean mPlaySounds; | ||||
|     @SuppressWarnings("WeakerAccess") protected boolean mPictureMetering; | ||||
|     @SuppressWarnings("WeakerAccess") protected boolean mPictureSnapshotMetering; | ||||
|     @SuppressWarnings("WeakerAccess") protected float mPreviewFrameRate; | ||||
|     @SuppressWarnings("WeakerAccess") private boolean mPreviewFrameRateExact; | ||||
| 
 | ||||
|     private FrameManager mFrameManager; | ||||
|     private final Angles mAngles = new Angles(); | ||||
|     @Nullable private SizeSelector mPreviewStreamSizeSelector; | ||||
|     private SizeSelector mPictureSizeSelector; | ||||
|     private SizeSelector mVideoSizeSelector; | ||||
|     private Facing mFacing; | ||||
|     private Mode mMode; | ||||
|     private Audio mAudio; | ||||
|     private long mVideoMaxSize; | ||||
|     private int mVideoMaxDuration; | ||||
|     private int mVideoBitRate; | ||||
|     private int mAudioBitRate; | ||||
|     private long mAutoFocusResetDelayMillis; | ||||
|     private int mSnapshotMaxWidth; // in REF_VIEW like SizeSelectors
 | ||||
|     private int mSnapshotMaxHeight; // in REF_VIEW like SizeSelectors
 | ||||
|     private int mFrameProcessingMaxWidth; // in REF_VIEW like SizeSelectors
 | ||||
|     private int mFrameProcessingMaxHeight; // in REF_VIEW like SizeSelectors
 | ||||
|     private int mFrameProcessingPoolSize; | ||||
|     private Overlay mOverlay; | ||||
| 
 | ||||
|     // Ops used for testing.
 | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mZoomTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mExposureCorrectionTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mFlashTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mWhiteBalanceTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mHdrTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mLocationTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPlaySoundsTask | ||||
|             = Tasks.forResult(null); | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPreviewFrameRateTask | ||||
|             = Tasks.forResult(null); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected CameraBaseEngine(@NonNull Callback callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called at construction time to get a frame manager that can later be | ||||
|      * accessed through {@link #getFrameManager()}. | ||||
|      * @param poolSize pool size | ||||
|      * @return a frame manager | ||||
|      */ | ||||
|     @NonNull | ||||
|     protected abstract FrameManager instantiateFrameManager(int poolSize); | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Angles getAngles() { | ||||
|         return mAngles; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public FrameManager getFrameManager() { | ||||
|         if (mFrameManager == null) { | ||||
|             mFrameManager = instantiateFrameManager(mFrameProcessingPoolSize); | ||||
|         } | ||||
|         return mFrameManager; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final CameraOptions getCameraOptions() { | ||||
|         return mCameraOptions; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPreview(@NonNull CameraPreview cameraPreview) { | ||||
|         if (mPreview != null) mPreview.setSurfaceCallback(null); | ||||
|         mPreview = cameraPreview; | ||||
|         mPreview.setSurfaceCallback(this); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final CameraPreview getPreview() { | ||||
|         return mPreview; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setOverlay(@Nullable Overlay overlay) { | ||||
|         mOverlay = overlay; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Overlay getOverlay() { | ||||
|         return mOverlay; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) { | ||||
|         mPreviewStreamSizeSelector = selector; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final SizeSelector getPreviewStreamSizeSelector() { | ||||
|         return mPreviewStreamSizeSelector; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPictureSizeSelector(@NonNull SizeSelector selector) { | ||||
|         mPictureSizeSelector = selector; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final SizeSelector getPictureSizeSelector() { | ||||
|         return mPictureSizeSelector; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setVideoSizeSelector(@NonNull SizeSelector selector) { | ||||
|         mVideoSizeSelector = selector; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final SizeSelector getVideoSizeSelector() { | ||||
|         return mVideoSizeSelector; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setVideoMaxSize(long videoMaxSizeBytes) { | ||||
|         mVideoMaxSize = videoMaxSizeBytes; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final long getVideoMaxSize() { | ||||
|         return mVideoMaxSize; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setVideoMaxDuration(int videoMaxDurationMillis) { | ||||
|         mVideoMaxDuration = videoMaxDurationMillis; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getVideoMaxDuration() { | ||||
|         return mVideoMaxDuration; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setVideoCodec(@NonNull VideoCodec codec) { | ||||
|         mVideoCodec = codec; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final VideoCodec getVideoCodec() { | ||||
|         return mVideoCodec; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setVideoBitRate(int videoBitRate) { | ||||
|         mVideoBitRate = videoBitRate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getVideoBitRate() { | ||||
|         return mVideoBitRate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setAudioCodec(@NonNull AudioCodec codec) { | ||||
|         mAudioCodec = codec; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final AudioCodec getAudioCodec() { | ||||
|         return mAudioCodec; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setAudioBitRate(int audioBitRate) { | ||||
|         mAudioBitRate = audioBitRate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getAudioBitRate() { | ||||
|         return mAudioBitRate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setSnapshotMaxWidth(int maxWidth) { | ||||
|         mSnapshotMaxWidth = maxWidth; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getSnapshotMaxWidth() { | ||||
|         return mSnapshotMaxWidth; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setSnapshotMaxHeight(int maxHeight) { | ||||
|         mSnapshotMaxHeight = maxHeight; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getSnapshotMaxHeight() { | ||||
|         return mSnapshotMaxHeight; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setFrameProcessingMaxWidth(int maxWidth) { | ||||
|         mFrameProcessingMaxWidth = maxWidth; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getFrameProcessingMaxWidth() { | ||||
|         return mFrameProcessingMaxWidth; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setFrameProcessingMaxHeight(int maxHeight) { | ||||
|         mFrameProcessingMaxHeight = maxHeight; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getFrameProcessingMaxHeight() { | ||||
|         return mFrameProcessingMaxHeight; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getFrameProcessingFormat() { | ||||
|         return mFrameProcessingFormat; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setFrameProcessingPoolSize(int poolSize) { | ||||
|         mFrameProcessingPoolSize = poolSize; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getFrameProcessingPoolSize() { | ||||
|         return mFrameProcessingPoolSize; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setAutoFocusResetDelay(long delayMillis) { | ||||
|         mAutoFocusResetDelayMillis = delayMillis; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final long getAutoFocusResetDelay() { | ||||
|         return mAutoFocusResetDelayMillis; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Helper function for subclasses. | ||||
|      * @return true if AF should be reset | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final boolean shouldResetAutoFocus() { | ||||
|         return mAutoFocusResetDelayMillis > 0 && mAutoFocusResetDelayMillis != Long.MAX_VALUE; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets a new facing value. This will restart the engine session (if there's any) | ||||
|      * so that we can open the new facing camera. | ||||
|      * @param facing facing | ||||
|      */ | ||||
|     @Override | ||||
|     public final void setFacing(final @NonNull Facing facing) { | ||||
|         final Facing old = mFacing; | ||||
|         if (facing != old) { | ||||
|             mFacing = facing; | ||||
|             getOrchestrator().scheduleStateful("facing", CameraState.ENGINE, | ||||
|                     new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     if (collectCameraInfo(facing)) { | ||||
|                         restart(); | ||||
|                     } else { | ||||
|                         mFacing = old; | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Facing getFacing() { | ||||
|         return mFacing; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets a new audio value that will be used for video recordings. | ||||
|      * @param audio desired audio | ||||
|      */ | ||||
|     @Override | ||||
|     public final void setAudio(@NonNull Audio audio) { | ||||
|         if (mAudio != audio) { | ||||
|             if (isTakingVideo()) { | ||||
|                 LOG.w("Audio setting was changed while recording. " + | ||||
|                         "Changes will take place starting from next video"); | ||||
|             } | ||||
|             mAudio = audio; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Audio getAudio() { | ||||
|         return mAudio; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the desired mode (either picture or video). | ||||
|      * @param mode desired mode. | ||||
|      */ | ||||
|     @Override | ||||
|     public final void setMode(@NonNull Mode mode) { | ||||
|         if (mode != mMode) { | ||||
|             mMode = mode; | ||||
|             getOrchestrator().scheduleStateful("mode", CameraState.ENGINE, | ||||
|                     new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     restart(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Mode getMode() { | ||||
|         return mMode; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final float getZoomValue() { | ||||
|         return mZoomValue; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final float getExposureCorrectionValue() { | ||||
|         return mExposureCorrectionValue; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Flash getFlash() { | ||||
|         return mFlash; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final WhiteBalance getWhiteBalance() { | ||||
|         return mWhiteBalance; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final Hdr getHdr() { | ||||
|         return mHdr; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Location getLocation() { | ||||
|         return mLocation; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public final PictureFormat getPictureFormat() { | ||||
|         return mPictureFormat; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPreviewFrameRateExact(boolean previewFrameRateExact) { | ||||
|         mPreviewFrameRateExact = previewFrameRateExact; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean getPreviewFrameRateExact() { | ||||
|         return mPreviewFrameRateExact; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final float getPreviewFrameRate() { | ||||
|         return mPreviewFrameRate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean hasFrameProcessors() { | ||||
|         return mHasFrameProcessors; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPictureMetering(boolean enable) { | ||||
|         mPictureMetering = enable; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean getPictureMetering() { | ||||
|         return mPictureMetering; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPictureSnapshotMetering(boolean enable) { | ||||
|         mPictureSnapshotMetering = enable; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean getPictureSnapshotMetering() { | ||||
|         return mPictureSnapshotMetering; | ||||
|     } | ||||
| 
 | ||||
|     //region Picture and video control
 | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean isTakingPicture() { | ||||
|         return mPictureRecorder != null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public /* final */ void takePicture(final @NonNull PictureResult.Stub stub) { | ||||
|         // Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
 | ||||
|         final boolean metering = mPictureMetering; | ||||
|         getOrchestrator().scheduleStateful("take picture", CameraState.BIND, | ||||
|                 new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("takePicture:", "running. isTakingPicture:", isTakingPicture()); | ||||
|                 if (isTakingPicture()) return; | ||||
|                 if (mMode == Mode.VIDEO) { | ||||
|                     throw new IllegalStateException("Can't take hq pictures while in VIDEO mode"); | ||||
|                 } | ||||
|                 stub.isSnapshot = false; | ||||
|                 stub.location = mLocation; | ||||
|                 stub.facing = mFacing; | ||||
|                 stub.format = mPictureFormat; | ||||
|                 onTakePicture(stub, metering); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The snapshot size is the {@link #getPreviewStreamSize(Reference)}, but cropped based on the | ||||
|      * view/surface aspect ratio. | ||||
|      * @param stub a picture stub | ||||
|      */ | ||||
|     @Override | ||||
|     public /* final */ void takePictureSnapshot(final @NonNull PictureResult.Stub stub) { | ||||
|         // Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
 | ||||
|         final boolean metering = mPictureSnapshotMetering; | ||||
|         getOrchestrator().scheduleStateful("take picture snapshot", CameraState.BIND, | ||||
|                 new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("takePictureSnapshot:", "running. isTakingPicture:", isTakingPicture()); | ||||
|                 if (isTakingPicture()) return; | ||||
|                 stub.location = mLocation; | ||||
|                 stub.isSnapshot = true; | ||||
|                 stub.facing = mFacing; | ||||
|                 stub.format = PictureFormat.JPEG; | ||||
|                 // Leave the other parameters to subclasses.
 | ||||
|                 //noinspection ConstantConditions
 | ||||
|                 AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); | ||||
|                 onTakePictureSnapshot(stub, ratio, metering); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onPictureShutter(boolean didPlaySound) { | ||||
|         getCallback().dispatchOnPictureShutter(!didPlaySound); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onPictureResult(@Nullable PictureResult.Stub result, @Nullable Exception error) { | ||||
|         mPictureRecorder = null; | ||||
|         if (result != null && result.data != null) { | ||||
|             getCallback().dispatchOnPictureTaken(result); | ||||
|         } else { | ||||
|             LOG.e("onPictureResult", "result or data is null: something went wrong.", error); | ||||
|             getCallback().dispatchError(new CameraException(error, | ||||
|                     CameraException.REASON_PICTURE_FAILED)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean isTakingVideo() { | ||||
|         return mVideoRecorder != null && mVideoRecorder.isRecording(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void takeVideo(final @NonNull VideoResult.Stub stub, | ||||
|                                 final @Nullable File file, | ||||
|                                 final @Nullable FileDescriptor fileDescriptor) { | ||||
|         getOrchestrator().scheduleStateful("take video", CameraState.BIND, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("takeVideo:", "running. isTakingVideo:", isTakingVideo()); | ||||
|                 if (isTakingVideo()) return; | ||||
|                 if (mMode == Mode.PICTURE) { | ||||
|                     throw new IllegalStateException("Can't record video while in PICTURE mode"); | ||||
|                 } | ||||
|                 if (file != null) { | ||||
|                     stub.file = file; | ||||
|                 } else if (fileDescriptor != null) { | ||||
|                     stub.fileDescriptor = fileDescriptor; | ||||
|                 } else { | ||||
|                     throw new IllegalStateException("file and fileDescriptor are both null."); | ||||
|                 } | ||||
|                 stub.isSnapshot = false; | ||||
|                 stub.videoCodec = mVideoCodec; | ||||
|                 stub.audioCodec = mAudioCodec; | ||||
|                 stub.location = mLocation; | ||||
|                 stub.facing = mFacing; | ||||
|                 stub.audio = mAudio; | ||||
|                 stub.maxSize = mVideoMaxSize; | ||||
|                 stub.maxDuration = mVideoMaxDuration; | ||||
|                 stub.videoBitRate = mVideoBitRate; | ||||
|                 stub.audioBitRate = mAudioBitRate; | ||||
|                 onTakeVideo(stub); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param stub a video stub | ||||
|      * @param file the output file | ||||
|      */ | ||||
|     @Override | ||||
|     public final void takeVideoSnapshot(@NonNull final VideoResult.Stub stub, | ||||
|                                         @NonNull final File file) { | ||||
|         getOrchestrator().scheduleStateful("take video snapshot", CameraState.BIND, | ||||
|                 new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("takeVideoSnapshot:", "running. isTakingVideo:", isTakingVideo()); | ||||
|                 stub.file = file; | ||||
|                 stub.isSnapshot = true; | ||||
|                 stub.videoCodec = mVideoCodec; | ||||
|                 stub.audioCodec = mAudioCodec; | ||||
|                 stub.location = mLocation; | ||||
|                 stub.facing = mFacing; | ||||
|                 stub.videoBitRate = mVideoBitRate; | ||||
|                 stub.audioBitRate = mAudioBitRate; | ||||
|                 stub.audio = mAudio; | ||||
|                 stub.maxSize = mVideoMaxSize; | ||||
|                 stub.maxDuration = mVideoMaxDuration; | ||||
|                 //noinspection ConstantConditions
 | ||||
|                 AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); | ||||
|                 onTakeVideoSnapshot(stub, ratio); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void stopVideo() { | ||||
|         getOrchestrator().schedule("stop video", true, new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 LOG.i("stopVideo", "running. isTakingVideo?", isTakingVideo()); | ||||
|                 onStopVideo(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @EngineThread | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected void onStopVideo() { | ||||
|         if (mVideoRecorder != null) { | ||||
|             mVideoRecorder.stop(false); | ||||
|             // Do not null this, so we respond correctly to isTakingVideo(),
 | ||||
|             // which checks for recorder presence and recorder.isRecording().
 | ||||
|             // It will be nulled in onVideoResult.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @CallSuper | ||||
|     @Override | ||||
|     public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { | ||||
|         mVideoRecorder = null; | ||||
|         if (result != null) { | ||||
|             getCallback().dispatchOnVideoTaken(result); | ||||
|         } else { | ||||
|             LOG.e("onVideoResult", "result is null: something went wrong.", exception); | ||||
|             getCallback().dispatchError(new CameraException(exception, | ||||
|                     CameraException.REASON_VIDEO_FAILED)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onVideoRecordingStart() { | ||||
|         getCallback().dispatchOnVideoRecordingStart(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onVideoRecordingEnd() { | ||||
|         getCallback().dispatchOnVideoRecordingEnd(); | ||||
|     } | ||||
| 
 | ||||
|     @EngineThread | ||||
|     protected abstract void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering); | ||||
| 
 | ||||
|     @EngineThread | ||||
|     protected abstract void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, | ||||
|                                                   @NonNull AspectRatio outputRatio, | ||||
|                                                   boolean doMetering); | ||||
| 
 | ||||
|     @EngineThread | ||||
|     protected abstract void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, | ||||
|                                                 @NonNull AspectRatio outputRatio); | ||||
| 
 | ||||
|     @EngineThread | ||||
|     protected abstract void onTakeVideo(@NonNull VideoResult.Stub stub); | ||||
| 
 | ||||
|     //endregion
 | ||||
| 
 | ||||
|     //region Size / Surface
 | ||||
| 
 | ||||
|     @Override | ||||
|     public final void onSurfaceChanged() { | ||||
|         LOG.i("onSurfaceChanged:", "Size is", getPreviewSurfaceSize(Reference.VIEW)); | ||||
|         getOrchestrator().scheduleStateful("surface changed", CameraState.BIND, | ||||
|                 new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 // Compute a new camera preview size and apply.
 | ||||
|                 Size newSize = computePreviewStreamSize(); | ||||
|                 if (newSize.equals(mPreviewStreamSize)) { | ||||
|                     LOG.i("onSurfaceChanged:", | ||||
|                             "The computed preview size is identical. No op."); | ||||
|                 } else { | ||||
|                     LOG.i("onSurfaceChanged:", | ||||
|                             "Computed a new preview size. Calling onPreviewStreamSizeChanged()."); | ||||
|                     mPreviewStreamSize = newSize; | ||||
|                     onPreviewStreamSizeChanged(); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The preview stream size has changed. At this point, some engine might want to | ||||
|      * simply call {@link #restartPreview()}, others to {@link #restartBind()}. | ||||
|      * | ||||
|      * It basically depends on the step at which the preview stream size is actually used. | ||||
|      */ | ||||
|     @EngineThread | ||||
|     protected abstract void onPreviewStreamSizeChanged(); | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Size getPictureSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { | ||||
|         Size size = mCaptureSize; | ||||
|         if (size == null || mMode == Mode.VIDEO) return null; | ||||
|         return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Size getVideoSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { | ||||
|         Size size = mCaptureSize; | ||||
|         if (size == null || mMode == Mode.PICTURE) return null; | ||||
|         return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Size getPreviewStreamSize(@NonNull Reference reference) { | ||||
|         Size size = mPreviewStreamSize; | ||||
|         if (size == null) return null; | ||||
|         return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("SameParameterValue") | ||||
|     @Nullable | ||||
|     private Size getPreviewSurfaceSize(@NonNull Reference reference) { | ||||
|         CameraPreview preview = mPreview; | ||||
|         if (preview == null) return null; | ||||
|         return getAngles().flip(Reference.VIEW, reference) ? preview.getSurfaceSize().flip() | ||||
|                 : preview.getSurfaceSize(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the snapshot size, but not cropped with the view dimensions, which | ||||
|      * is what we will do before creating the snapshot. However, cropping is done at various | ||||
|      * levels so we don't want to perform the op here. | ||||
|      * | ||||
|      * The base snapshot size is based on PreviewStreamSize (later cropped with view ratio). Why? | ||||
|      * One might be tempted to say that it's the SurfaceSize (which already matches the view ratio). | ||||
|      * | ||||
|      * The camera sensor will capture preview frames with PreviewStreamSize and that's it. Then they | ||||
|      * are hardware-scaled by the preview surface, but this does not affect the snapshot, as the | ||||
|      * snapshot recorder simply creates another surface. | ||||
|      * | ||||
|      * Done tests to ensure that this is true, by using | ||||
|      * 1. small SurfaceSize and biggest() PreviewStreamSize: output is not low quality | ||||
|      * 2. big SurfaceSize and smallest() PreviewStreamSize: output is low quality | ||||
|      * In both cases the result.size here was set to the biggest of the two. | ||||
|      * | ||||
|      * I could not find the same evidence for videos, but I would say that the same things should | ||||
|      * apply, despite the capturing mechanism being different. | ||||
|      * | ||||
|      * @param reference the reference system | ||||
|      * @return the uncropped snapshot size | ||||
|      */ | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public final Size getUncroppedSnapshotSize(@NonNull Reference reference) { | ||||
|         Size baseSize = getPreviewStreamSize(reference); | ||||
|         if (baseSize == null) return null; | ||||
|         boolean flip = getAngles().flip(reference, Reference.VIEW); | ||||
|         int maxWidth = flip ? mSnapshotMaxHeight : mSnapshotMaxWidth; | ||||
|         int maxHeight = flip ? mSnapshotMaxWidth : mSnapshotMaxHeight; | ||||
|         if (maxWidth <= 0) maxWidth = Integer.MAX_VALUE; | ||||
|         if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE; | ||||
|         float baseRatio = AspectRatio.of(baseSize).toFloat(); | ||||
|         float maxValuesRatio = AspectRatio.of(maxWidth, maxHeight).toFloat(); | ||||
|         if (maxValuesRatio >= baseRatio) { | ||||
|             // Height is the real constraint.
 | ||||
|             int outHeight = Math.min(baseSize.getHeight(), maxHeight); | ||||
|             int outWidth = (int) Math.floor((float) outHeight * baseRatio); | ||||
|             return new Size(outWidth, outHeight); | ||||
|         } else { | ||||
|             // Width is the real constraint.
 | ||||
|             int outWidth = Math.min(baseSize.getWidth(), maxWidth); | ||||
|             int outHeight = (int) Math.floor((float) outWidth / baseRatio); | ||||
|             return new Size(outWidth, outHeight); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This is called either on cameraView.start(), or when the underlying surface changes. | ||||
|      * It is possible that in the first call the preview surface has not already computed its | ||||
|      * dimensions. | ||||
|      * But when it does, the {@link CameraPreview.SurfaceCallback} should be called, | ||||
|      * and this should be refreshed. | ||||
|      * | ||||
|      * @return the capture size | ||||
|      */ | ||||
|     @NonNull | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final Size computeCaptureSize() { | ||||
|         return computeCaptureSize(mMode); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final Size computeCaptureSize(@NonNull Mode mode) { | ||||
|         // We want to pass stuff into the REF_VIEW reference, not the sensor one.
 | ||||
|         // This is already managed by CameraOptions, so we just flip again at the end.
 | ||||
|         boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | ||||
|         SizeSelector selector; | ||||
|         Collection<Size> sizes; | ||||
|         if (mode == Mode.PICTURE) { | ||||
|             selector = mPictureSizeSelector; | ||||
|             sizes = mCameraOptions.getSupportedPictureSizes(); | ||||
|         } else { | ||||
|             selector = mVideoSizeSelector; | ||||
|             sizes = mCameraOptions.getSupportedVideoSizes(); | ||||
|         } | ||||
|         selector = SizeSelectors.or(selector, SizeSelectors.biggest()); | ||||
|         List<Size> list = new ArrayList<>(sizes); | ||||
|         Size result = selector.select(list).get(0); | ||||
|         if (!list.contains(result)) { | ||||
|             throw new RuntimeException("SizeSelectors must not return Sizes other than " + | ||||
|                     "those in the input list."); | ||||
|         } | ||||
|         LOG.i("computeCaptureSize:", "result:", result, "flip:", flip, "mode:", mode); | ||||
|         if (flip) result = result.flip(); // Go back to REF_SENSOR
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This is called anytime {@link #computePreviewStreamSize()} is called. | ||||
|      * This means that it should be called during the binding process, when | ||||
|      * we can be sure that the camera is available (engineState == STARTED). | ||||
|      * @return a list of available sizes for preview | ||||
|      */ | ||||
|     @EngineThread | ||||
|     @NonNull | ||||
|     protected abstract List<Size> getPreviewStreamAvailableSizes(); | ||||
| 
 | ||||
|     @EngineThread | ||||
|     @NonNull | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final Size computePreviewStreamSize() { | ||||
|         @NonNull List<Size> previewSizes = getPreviewStreamAvailableSizes(); | ||||
|         // These sizes come in REF_SENSOR. Since there is an external selector involved,
 | ||||
|         // we must convert all of them to REF_VIEW, then flip back when returning.
 | ||||
|         boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | ||||
|         List<Size> sizes = new ArrayList<>(previewSizes.size()); | ||||
|         for (Size size : previewSizes) { | ||||
|             sizes.add(flip ? size.flip() : size); | ||||
|         } | ||||
| 
 | ||||
|         // Create our own default selector, which will be used if the external
 | ||||
|         // mPreviewStreamSizeSelector is null, or if it fails in finding a size.
 | ||||
|         Size targetMinSize = getPreviewSurfaceSize(Reference.VIEW); | ||||
|         if (targetMinSize == null) { | ||||
|             throw new IllegalStateException("targetMinSize should not be null here."); | ||||
|         } | ||||
|         AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); | ||||
|         if (flip) targetRatio = targetRatio.flip(); | ||||
|         LOG.i("computePreviewStreamSize:", | ||||
|                 "targetRatio:", targetRatio, | ||||
|                 "targetMinSize:", targetMinSize); | ||||
|         SizeSelector matchRatio = SizeSelectors.and( // Match this aspect ratio and sort by biggest
 | ||||
|                 SizeSelectors.aspectRatio(targetRatio, 0), | ||||
|                 SizeSelectors.biggest()); | ||||
|         SizeSelector matchSize = SizeSelectors.and( // Bigger than this size, and sort by smallest
 | ||||
|                 SizeSelectors.minHeight(targetMinSize.getHeight()), | ||||
|                 SizeSelectors.minWidth(targetMinSize.getWidth()), | ||||
|                 SizeSelectors.smallest()); | ||||
|         SizeSelector matchAll = SizeSelectors.or( | ||||
|                 SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
 | ||||
|                 matchSize, // If couldn't match aspect ratio, at least respect the size
 | ||||
|                 matchRatio, // If couldn't respect size, at least match aspect ratio
 | ||||
|                 SizeSelectors.biggest() // If couldn't match any, take the biggest.
 | ||||
|         ); | ||||
| 
 | ||||
|         // Apply the external selector with this as a fallback,
 | ||||
|         // and return a size in REF_SENSOR reference.
 | ||||
|         SizeSelector selector; | ||||
|         if (mPreviewStreamSizeSelector != null) { | ||||
|             selector = SizeSelectors.or(mPreviewStreamSizeSelector, matchAll); | ||||
|         } else { | ||||
|             selector = matchAll; | ||||
|         } | ||||
|         Size result = selector.select(sizes).get(0); | ||||
|         if (!sizes.contains(result)) { | ||||
|             throw new RuntimeException("SizeSelectors must not return Sizes other than " + | ||||
|                     "those in the input list."); | ||||
|         } | ||||
|         if (flip) result = result.flip(); | ||||
|         LOG.i("computePreviewStreamSize:", "result:", result, "flip:", flip); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This is called anytime {@link #computeFrameProcessingSize()} is called. | ||||
|      * Implementors can return null if frame processor size is not selectable | ||||
|      * @return a list of available sizes for frame processing | ||||
|      */ | ||||
|     @EngineThread | ||||
|     @NonNull | ||||
|     protected abstract List<Size> getFrameProcessingAvailableSizes(); | ||||
| 
 | ||||
|     @EngineThread | ||||
|     @NonNull | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final Size computeFrameProcessingSize() { | ||||
|         @NonNull List<Size> frameSizes = getFrameProcessingAvailableSizes(); | ||||
|         // These sizes come in REF_SENSOR. Since there is an external selector involved,
 | ||||
|         // we must convert all of them to REF_VIEW, then flip back when returning.
 | ||||
|         boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | ||||
|         List<Size> sizes = new ArrayList<>(frameSizes.size()); | ||||
|         for (Size size : frameSizes) { | ||||
|             sizes.add(flip ? size.flip() : size); | ||||
|         } | ||||
|         AspectRatio targetRatio = AspectRatio.of( | ||||
|                 mPreviewStreamSize.getWidth(), | ||||
|                 mPreviewStreamSize.getHeight()); | ||||
|         if (flip) targetRatio = targetRatio.flip(); | ||||
|         int maxWidth = mFrameProcessingMaxWidth; | ||||
|         int maxHeight = mFrameProcessingMaxHeight; | ||||
|         if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) maxWidth = 640; | ||||
|         if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) maxHeight = 640; | ||||
|         Size targetMaxSize = new Size(maxWidth, maxHeight); | ||||
|         LOG.i("computeFrameProcessingSize:", | ||||
|                 "targetRatio:", targetRatio, | ||||
|                 "targetMaxSize:", targetMaxSize); | ||||
|         SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); | ||||
|         SizeSelector matchSize = SizeSelectors.and( | ||||
|                 SizeSelectors.maxHeight(targetMaxSize.getHeight()), | ||||
|                 SizeSelectors.maxWidth(targetMaxSize.getWidth()), | ||||
|                 SizeSelectors.biggest()); | ||||
|         SizeSelector matchAll = SizeSelectors.or( | ||||
|                 SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
 | ||||
|                 matchSize, // If couldn't match aspect ratio, at least respect the size
 | ||||
|                 SizeSelectors.smallest() // If couldn't match any, take the smallest.
 | ||||
|         ); | ||||
|         Size result = matchAll.select(sizes).get(0); | ||||
|         if (!sizes.contains(result)) { | ||||
|             throw new RuntimeException("SizeSelectors must not return Sizes other than " + | ||||
|                     "those in the input list."); | ||||
|         } | ||||
|         if (flip) result = result.flip(); | ||||
|         LOG.i("computeFrameProcessingSize:", "result:", result, "flip:", flip); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     //endregion
 | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -1,7 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| /** | ||||
|  * Indicates that some action is being executed on the {@link CameraEngine} thread. | ||||
|  */ | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public @interface EngineThread {} | ||||
| @ -0,0 +1,221 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.hardware.camera2.CameraCharacteristics; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.controls.Control; | ||||
| import com.otaliastudios.cameraview.controls.Engine; | ||||
| 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 java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * A Mapper maps camera engine constants to CameraView constants. | ||||
|  */ | ||||
| public abstract class Mapper { | ||||
| 
 | ||||
|     private static Mapper CAMERA1; | ||||
|     private static Mapper CAMERA2; | ||||
| 
 | ||||
|     public static Mapper get(@NonNull Engine engine) { | ||||
|         if (engine == Engine.CAMERA1) { | ||||
|             if (CAMERA1 == null) CAMERA1 = new Camera1Mapper(); | ||||
|             return CAMERA1; | ||||
|         } else if (engine == Engine.CAMERA2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|             if (CAMERA2 == null) CAMERA2 = new Camera2Mapper(); | ||||
|             return CAMERA2; | ||||
|         } else { | ||||
|             throw new IllegalArgumentException("Unknown engine or unsupported API level."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Mapper() {} | ||||
| 
 | ||||
|     public abstract <T> T map(Flash flash); | ||||
| 
 | ||||
|     public abstract <T> T map(Facing facing); | ||||
| 
 | ||||
|     public abstract <T> T map(WhiteBalance whiteBalance); | ||||
| 
 | ||||
|     public abstract <T> T map(Hdr hdr); | ||||
| 
 | ||||
|     public abstract <T> Flash unmapFlash(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> Facing unmapFacing(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> Hdr unmapHdr(T cameraConstant); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected <C extends Control, T> C reverseLookup(HashMap<C, T> map, T object) { | ||||
|         for (C value : map.keySet()) { | ||||
|             if (object.equals(map.get(value))) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected <C extends Control, T> C reverseListLookup(HashMap<C, List<T>> map, T object) { | ||||
|         for (C value : map.keySet()) { | ||||
|             List<T> list = map.get(value); | ||||
|             if (list == null) continue; | ||||
|             for (T candidate : list) { | ||||
|                 if (object.equals(candidate)) { | ||||
|                     return value; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static class Camera1Mapper extends Mapper { | ||||
| 
 | ||||
|         private static final HashMap<Flash, String> FLASH = new HashMap<>(); | ||||
|         private static final HashMap<WhiteBalance, String> WB = new HashMap<>(); | ||||
|         private static final HashMap<Facing, Integer> FACING = new HashMap<>(); | ||||
|         private static final HashMap<Hdr, String> HDR = new HashMap<>(); | ||||
| 
 | ||||
|         static { | ||||
|             FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); | ||||
|             FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); | ||||
|             FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); | ||||
|             FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); | ||||
|             FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); | ||||
|             FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); | ||||
|             WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); | ||||
|             WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); | ||||
|             WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); | ||||
|             WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); | ||||
|             WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); | ||||
|             HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); | ||||
|             if (Build.VERSION.SDK_INT >= 17) { | ||||
|                 HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); | ||||
|             } else { | ||||
|                 HDR.put(Hdr.ON, "hdr"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Flash flash) { | ||||
|             return (T) FLASH.get(flash); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Facing facing) { | ||||
|             return (T) FACING.get(facing); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(WhiteBalance whiteBalance) { | ||||
|             return (T) WB.get(whiteBalance); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Hdr hdr) { | ||||
|             return (T) HDR.get(hdr); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Flash unmapFlash(T cameraConstant) { | ||||
|             return reverseLookup(FLASH, (String) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Facing unmapFacing(T cameraConstant) { | ||||
|             return reverseLookup(FACING, (Integer) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { | ||||
|             return reverseLookup(WB, (String) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Hdr unmapHdr(T cameraConstant) { | ||||
|             return reverseLookup(HDR, (String) cameraConstant); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     private static class Camera2Mapper extends Mapper { | ||||
| 
 | ||||
|         private static final HashMap<Flash, List<Integer>> FLASH = new HashMap<>(); | ||||
|         private static final HashMap<Facing, Integer> FACING = new HashMap<>(); | ||||
|         private static final HashMap<WhiteBalance, Integer> WB = new HashMap<>(); | ||||
|         private static final HashMap<Hdr, Integer> HDR = new HashMap<>(); | ||||
| 
 | ||||
|         static { | ||||
|             // OFF and TORCH have also a second condition - to CameraCharacteristics.CONTROL_FLASH_MODE - but that does not
 | ||||
|             // fit into the Mapper interface. TODO review this
 | ||||
|             FLASH.put(Flash.OFF, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); | ||||
|             FLASH.put(Flash.TORCH, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); | ||||
|             FLASH.put(Flash.AUTO, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH, CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE)); | ||||
|             FLASH.put(Flash.ON, Collections.singletonList(CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH)); | ||||
|             FACING.put(Facing.BACK, CameraCharacteristics.LENS_FACING_BACK); | ||||
|             FACING.put(Facing.FRONT, CameraCharacteristics.LENS_FACING_FRONT); | ||||
|             WB.put(WhiteBalance.AUTO, CameraCharacteristics.CONTROL_AWB_MODE_AUTO); | ||||
|             WB.put(WhiteBalance.CLOUDY, CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); | ||||
|             WB.put(WhiteBalance.DAYLIGHT, CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT); | ||||
|             WB.put(WhiteBalance.FLUORESCENT, CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT); | ||||
|             WB.put(WhiteBalance.INCANDESCENT, CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT); | ||||
|             HDR.put(Hdr.OFF, CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED); | ||||
|             HDR.put(Hdr.ON, 18 /* CameraCharacteristics.CONTROL_SCENE_MODE_HDR */); | ||||
|         } | ||||
| 
 | ||||
|         @SuppressWarnings("ConstantConditions") | ||||
|         @Override | ||||
|         public <T> T map(Flash flash) { | ||||
|             return (T) FLASH.get(flash); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Facing facing) { | ||||
|             return (T) FACING.get(facing); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(WhiteBalance whiteBalance) { | ||||
|             return (T) WB.get(whiteBalance); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Hdr hdr) { | ||||
|             return (T) HDR.get(hdr); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Flash unmapFlash(T cameraConstant) { | ||||
|             return reverseListLookup(FLASH, (Integer) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Facing unmapFacing(T cameraConstant) { | ||||
|             return reverseLookup(FACING, (Integer) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { | ||||
|             return reverseLookup(WB, (Integer) cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Hdr unmapHdr(T cameraConstant) { | ||||
|             return reverseLookup(HDR, (Integer) cameraConstant); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,170 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import com.google.android.gms.tasks.Continuation; | ||||
| import com.google.android.gms.tasks.OnFailureListener; | ||||
| import com.google.android.gms.tasks.SuccessContinuation; | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.tasks.Tasks; | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| 
 | ||||
| import java.util.concurrent.Callable; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| /** | ||||
|  * Represents one of the steps in the {@link CameraEngine} setup: for example, the engine step, | ||||
|  * the bind-to-surface step, and the preview step. | ||||
|  * | ||||
|  * A step is something that can be setup (started) or torn down (stopped), and | ||||
|  * steps can of course depend onto each other. | ||||
|  * | ||||
|  * The purpose of this class is to manage the step state (stopping, stopped, starting or started) | ||||
|  * and, more importantly, to perform START and STOP operations in such a way that they do not | ||||
|  * overlap. For example, if we're stopping, we're wait for stop to finish before starting again. | ||||
|  * | ||||
|  * This is an important condition for simplifying the engine code. | ||||
|  * Since Camera1, the only requirement was basically to use a single thread. | ||||
|  * Since Camera2, which has an asynchronous API, further care must be used. | ||||
|  * | ||||
|  * For this reason, we use Google's {@link Task} abstraction and only start new operations | ||||
|  * once the previous one has ended. | ||||
|  * | ||||
|  * <strong>This class is NOT thread safe!</string> | ||||
|  */ | ||||
| class Step { | ||||
| 
 | ||||
|     private static final String TAG = Step.class.getSimpleName(); | ||||
|     private static final CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     interface Callback { | ||||
|         @NonNull | ||||
|         Executor getExecutor(); | ||||
|         void handleException(@NonNull Exception exception); | ||||
|     } | ||||
| 
 | ||||
|     static final int STATE_STOPPING = -1; | ||||
|     static final int STATE_STOPPED = 0; | ||||
|     static final int STATE_STARTING = 1; | ||||
|     static final int STATE_STARTED = 2; | ||||
| 
 | ||||
|     private int state = STATE_STOPPED; | ||||
| 
 | ||||
|     // To avoid dirty scenarios (e.g. calling stopXXX while XXX is starting),
 | ||||
|     // and since every operation can be asynchronous, we use some tasks for each step.
 | ||||
|     private Task<Void> task = Tasks.forResult(null); | ||||
| 
 | ||||
|     private final String name; | ||||
|     private final Callback callback; | ||||
| 
 | ||||
|     Step(@NonNull String name, @NonNull Callback callback) { | ||||
|         this.name = name.toUpperCase(); | ||||
|         this.callback = callback; | ||||
|     } | ||||
| 
 | ||||
|     int getState() { | ||||
|         return state; | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting void setState(int newState) { | ||||
|         state = newState; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     String getStateName() { | ||||
|         switch (state) { | ||||
|             case STATE_STOPPING: return name + "_STATE_STOPPING"; | ||||
|             case STATE_STOPPED: return name + "_STATE_STOPPED"; | ||||
|             case STATE_STARTING: return name + "_STATE_STARTING"; | ||||
|             case STATE_STARTED: return name + "_STATE_STARTED"; | ||||
|         } | ||||
|         return "null"; | ||||
|     } | ||||
| 
 | ||||
|     boolean isStoppingOrStopped() { | ||||
|         return state == STATE_STOPPING || state == STATE_STOPPED; | ||||
|     } | ||||
| 
 | ||||
|     boolean isStartedOrStarting() { | ||||
|         return state == STATE_STARTING || state == STATE_STARTED; | ||||
|     } | ||||
| 
 | ||||
|     boolean isStarted() { | ||||
|         return state == STATE_STARTED; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     Task<Void> getTask() { | ||||
|         return task; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"SameParameterValue", "UnusedReturnValue"}) | ||||
|     Task<Void> doStart(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op) { | ||||
|         return doStart(swallowExceptions, op, null); | ||||
|     } | ||||
| 
 | ||||
|     Task<Void> doStart(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op, final @Nullable Runnable onStarted) { | ||||
|         LOG.i(name, "doStart", "Called. Enqueuing."); | ||||
|         task = task.continueWithTask(callback.getExecutor(), new Continuation<Void, Task<Void>>() { | ||||
|             @Override | ||||
|             public Task<Void> then(@NonNull Task<Void> task) throws Exception { | ||||
|                 LOG.i(name, "doStart", "About to start. Setting state to STARTING"); | ||||
|                 setState(STATE_STARTING); | ||||
|                 return op.call().addOnFailureListener(callback.getExecutor(), new OnFailureListener() { | ||||
|                     @Override | ||||
|                     public void onFailure(@NonNull Exception e) { | ||||
|                         LOG.w(name, "doStart", "Failed with error", e, "Setting state to STOPPED"); | ||||
|                         setState(STATE_STOPPED); | ||||
|                         if (!swallowExceptions) callback.handleException(e); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }).onSuccessTask(callback.getExecutor(), new SuccessContinuation<Void, Void>() { | ||||
|             @NonNull | ||||
|             @Override | ||||
|             public Task<Void> then(@Nullable Void aVoid) { | ||||
|                 LOG.i(name, "doStart", "Succeeded! Setting state to STARTED"); | ||||
|                 setState(STATE_STARTED); | ||||
|                 if (onStarted != null) onStarted.run(); | ||||
|                 return Tasks.forResult(null); | ||||
|             } | ||||
|         }); | ||||
|         return task; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("UnusedReturnValue") | ||||
|     Task<Void> doStop(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op) { | ||||
|         return doStop(swallowExceptions, op, null); | ||||
|     } | ||||
| 
 | ||||
|     Task<Void> doStop(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op, final @Nullable Runnable onStopped) { | ||||
|         LOG.i(name, "doStop", "Called. Enqueuing."); | ||||
|         task = task.continueWithTask(callback.getExecutor(), new Continuation<Void, Task<Void>>() { | ||||
|             @Override | ||||
|             public Task<Void> then(@NonNull Task<Void> task) throws Exception { | ||||
|                 LOG.i(name, "doStop", "About to stop. Setting state to STOPPING"); | ||||
|                 state = STATE_STOPPING; | ||||
|                 return op.call().addOnFailureListener(callback.getExecutor(), new OnFailureListener() { | ||||
|                     @Override | ||||
|                     public void onFailure(@NonNull Exception e) { | ||||
|                         LOG.w(name, "doStop", "Failed with error", e, "Setting state to STOPPED"); | ||||
|                         state = STATE_STOPPED; | ||||
|                         if (!swallowExceptions) callback.handleException(e); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }).onSuccessTask(callback.getExecutor(), new SuccessContinuation<Void, Void>() { | ||||
|             @NonNull | ||||
|             @Override | ||||
|             public Task<Void> then(@Nullable Void aVoid) { | ||||
|                 LOG.i(name, "doStop", "Succeeded! Setting state to STOPPED"); | ||||
|                 state = STATE_STOPPED; | ||||
|                 if (onStopped != null) onStopped.run(); | ||||
|                 return Tasks.forResult(null); | ||||
|             } | ||||
|         }); | ||||
|         return task; | ||||
|     } | ||||
| } | ||||
| @ -1,92 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CameraCaptureSession; | ||||
| import android.hardware.camera2.CameraCaptureSession.CaptureCallback; | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * The Action class encapsulates logic for completing an action in a Camera2 environment. | ||||
|  * In this case, we are often interested in constantly receiving the {@link CaptureResult} | ||||
|  * and {@link CaptureRequest} callbacks, as well as applying changes to a | ||||
|  * {@link CaptureRequest.Builder} and having them applied to the sensor. | ||||
|  * | ||||
|  * The Action class receives the given callbacks and can operate over the engine | ||||
|  * through the {@link ActionHolder} object. | ||||
|  * | ||||
|  * Each Action operates on a given state in a given moment. This base class offers the | ||||
|  * {@link #STATE_COMPLETED} state which is common to all actions. | ||||
|  * | ||||
|  * See {@link BaseAction} for a base implementation. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public interface Action { | ||||
| 
 | ||||
|     int STATE_COMPLETED = Integer.MAX_VALUE; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the current state. | ||||
|      * @return the state | ||||
|      */ | ||||
|     int getState(); | ||||
| 
 | ||||
|     /** | ||||
|      * Starts this action. | ||||
|      * @param holder the holder | ||||
|      */ | ||||
|     void start(@NonNull ActionHolder holder); | ||||
| 
 | ||||
|     /** | ||||
|      * Aborts this action. | ||||
|      * @param holder the holder | ||||
|      */ | ||||
|     void abort(@NonNull ActionHolder holder); | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an {@link ActionCallback} to receive state | ||||
|      * change events. | ||||
|      * @param callback a callback | ||||
|      */ | ||||
|     void addCallback(@NonNull ActionCallback callback); | ||||
| 
 | ||||
|     /** | ||||
|      * Removes a previously added callback. | ||||
|      * @param callback a callback | ||||
|      */ | ||||
|     void removeCallback(@NonNull ActionCallback callback); | ||||
| 
 | ||||
|     /** | ||||
|      * Called from {@link CaptureCallback#onCaptureStarted(CameraCaptureSession, CaptureRequest, | ||||
|      * long, long)}. | ||||
|      * @param holder the holder | ||||
|      * @param request the request | ||||
|      */ | ||||
|     void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request); | ||||
| 
 | ||||
|     /** | ||||
|      * Called from {@link CaptureCallback#onCaptureProgressed(CameraCaptureSession, CaptureRequest, | ||||
|      * CaptureResult)}. | ||||
|      * @param holder the holder | ||||
|      * @param request the request | ||||
|      * @param result the result | ||||
|      */ | ||||
|     void onCaptureProgressed(@NonNull ActionHolder holder, | ||||
|                              @NonNull CaptureRequest request, | ||||
|                              @NonNull CaptureResult result); | ||||
| 
 | ||||
|     /** | ||||
|      * Called from {@link CaptureCallback#onCaptureCompleted(CameraCaptureSession, CaptureRequest, | ||||
|      * TotalCaptureResult)}. | ||||
|      * @param holder the holder | ||||
|      * @param request the request | ||||
|      * @param result the result | ||||
|      */ | ||||
|     void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                             @NonNull CaptureRequest request, | ||||
|                             @NonNull TotalCaptureResult result); | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * A callback for {@link Action} state changes. | ||||
|  * See the action class. | ||||
|  * | ||||
|  * See also {@link CompletionCallback}. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public interface ActionCallback { | ||||
| 
 | ||||
|     /** | ||||
|      * Action state has just changed. | ||||
|      * @param action action | ||||
|      * @param state new state | ||||
|      */ | ||||
|     void onActionStateChanged(@NonNull Action action, int state); | ||||
| } | ||||
| @ -1,83 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CameraAccessException; | ||||
| import android.hardware.camera2.CameraCharacteristics; | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * The holder of {@link Action}. | ||||
|  * | ||||
|  * This class should keep a list or set of currently running actions, and offers | ||||
|  * to them the base Camera2 objects that are needed to apply changes. | ||||
|  * | ||||
|  * This class, or an holder of it, should also forward the capture callbacks | ||||
|  * to all {@link Action}s. See {@link com.otaliastudios.cameraview.engine.Camera2Engine} for | ||||
|  * our implementation. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public interface ActionHolder { | ||||
| 
 | ||||
|     /** | ||||
|      * Adds a new action | ||||
|      * @param action action | ||||
|      */ | ||||
|     void addAction(@NonNull Action action); | ||||
| 
 | ||||
|     /** | ||||
|      * Removes a previously added action | ||||
|      * @param action action | ||||
|      */ | ||||
|     void removeAction(@NonNull Action action); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the {@link CameraCharacteristics} of the current | ||||
|      * camera device. | ||||
|      * @param action action | ||||
|      * @return characteristics | ||||
|      */ | ||||
|     @NonNull | ||||
|     CameraCharacteristics getCharacteristics(@NonNull Action action); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the latest {@link TotalCaptureResult}. Can be used | ||||
|      * by actions to start querying the state before receiving their | ||||
|      * first frame. | ||||
|      * @param action action | ||||
|      * @return last result | ||||
|      */ | ||||
|     @Nullable | ||||
|     TotalCaptureResult getLastResult(@NonNull Action action); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the current {@link CaptureRequest.Builder} so that | ||||
|      * actions can apply changes to it and later submit them. | ||||
|      * @param action action | ||||
|      * @return the builder | ||||
|      */ | ||||
|     @NonNull | ||||
|     CaptureRequest.Builder getBuilder(@NonNull Action action); | ||||
| 
 | ||||
|     /** | ||||
|      * Applies the current builder (as per {@link #getBuilder(Action)}) | ||||
|      * as a repeating request on the preview. | ||||
|      * @param source action | ||||
|      */ | ||||
|     void applyBuilder(@NonNull Action source); | ||||
| 
 | ||||
|     /** | ||||
|      * Applies the given builder as a single capture request. | ||||
|      * Callers can catch the exception and choose what to do. | ||||
|      * @param source action | ||||
|      * @param builder builder | ||||
|      * @throws CameraAccessException camera exception | ||||
|      */ | ||||
|     void applyBuilder(@NonNull Action source, @NonNull CaptureRequest.Builder builder) | ||||
|             throws CameraAccessException; | ||||
| } | ||||
| @ -1,67 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * A simple wrapper around a {@link BaseAction}. | ||||
|  * This can be used to add functionality around a base action. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public abstract class ActionWrapper extends BaseAction { | ||||
| 
 | ||||
|     /** | ||||
|      * Should return the wrapped action. | ||||
|      * @return the wrapped action | ||||
|      */ | ||||
|     @NonNull | ||||
|     public abstract BaseAction getAction(); | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart(@NonNull ActionHolder holder) { | ||||
|         super.onStart(holder); | ||||
|         getAction().addCallback(new ActionCallback() { | ||||
|             @Override | ||||
|             public void onActionStateChanged(@NonNull Action action, int state) { | ||||
|                 setState(state); | ||||
|                 if (state == STATE_COMPLETED) { | ||||
|                     action.removeCallback(this); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         getAction().onStart(holder); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onAbort(@NonNull ActionHolder holder) { | ||||
|         super.onAbort(holder); | ||||
|         getAction().onAbort(holder); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { | ||||
|         super.onCaptureStarted(holder, request); | ||||
|         getAction().onCaptureStarted(holder, request); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureProgressed(@NonNull ActionHolder holder, | ||||
|                                     @NonNull CaptureRequest request, | ||||
|                                     @NonNull CaptureResult result) { | ||||
|         super.onCaptureProgressed(holder, request, result); | ||||
|         getAction().onCaptureProgressed(holder, request, result); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         super.onCaptureCompleted(holder, request, result); | ||||
|         getAction().onCaptureCompleted(holder, request, result); | ||||
|     } | ||||
| } | ||||
| @ -1,56 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| /** | ||||
|  * Utilities for creating {@link Action} sequences. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public class Actions { | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a {@link BaseAction} that executes all the child actions | ||||
|      * together, at the same time, and completes once all of them are | ||||
|      * completed. | ||||
|      * | ||||
|      * @param actions input actions | ||||
|      * @return a new action | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static BaseAction together(@NonNull BaseAction... actions) { | ||||
|         return new TogetherAction(Arrays.asList(actions)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a {@link BaseAction} that executes all the child actions | ||||
|      * in sequence, waiting for the first to complete, then going on with | ||||
|      * the second and so on, finally completing when all are completed. | ||||
|      * | ||||
|      * @param actions input actions | ||||
|      * @return a new action | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static BaseAction sequence(@NonNull BaseAction... actions) { | ||||
|         return new SequenceAction(Arrays.asList(actions)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a {@link BaseAction} that completes as normal, but is also | ||||
|      * forced to complete if the given timeout is reached, by calling | ||||
|      * {@link Action#abort(ActionHolder)}. | ||||
|      * | ||||
|      * @param timeoutMillis timeout in milliseconds | ||||
|      * @param action action | ||||
|      * @return a new action | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static BaseAction timeout(long timeoutMillis, @NonNull BaseAction action) { | ||||
|         return new TimeoutAction(timeoutMillis, action); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,177 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CameraCharacteristics; | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.CallSuper; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * The base implementation of {@link Action} that should always be subclassed, | ||||
|  * instead of implementing the root interface itself. | ||||
|  * | ||||
|  * It holds a list of callbacks and dispatches events to them, plus it cares about | ||||
|  * its own lifecycle: | ||||
|  * - when {@link #start(ActionHolder)} is called, we add ourselves to the holder list | ||||
|  * - when {@link #STATE_COMPLETED} is reached, we remove ouverselves from the holder list | ||||
|  * | ||||
|  * This is very important in all cases. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public abstract class BaseAction implements Action { | ||||
| 
 | ||||
|     private final List<ActionCallback> callbacks = new ArrayList<>(); | ||||
|     private int state; | ||||
|     private ActionHolder holder; | ||||
|     private boolean needsOnStart; | ||||
| 
 | ||||
|     @Override | ||||
|     public final int getState() { | ||||
|         return state; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void start(@NonNull ActionHolder holder) { | ||||
|         this.holder = holder; | ||||
|         holder.addAction(this); | ||||
|         if (holder.getLastResult(this) != null) { | ||||
|             onStart(holder); | ||||
|         } else { | ||||
|             needsOnStart = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void abort(@NonNull ActionHolder holder) { | ||||
|         holder.removeAction(this); | ||||
|         if (!isCompleted()) { | ||||
|             onAbort(holder); | ||||
|             setState(STATE_COMPLETED); | ||||
|         } | ||||
|         needsOnStart = false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Action was started and will soon receive events from the | ||||
|      * holder stream. | ||||
|      * @param holder holder | ||||
|      */ | ||||
|     @CallSuper | ||||
|     protected void onStart(@NonNull ActionHolder holder) { | ||||
|         // Repeating holder assignment here (already in start()) because we NEED it in start()
 | ||||
|         // but some special actions will not call start() at all for their children.
 | ||||
|         this.holder = holder; | ||||
|         // Overrideable
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Action was aborted and will not receive events from the | ||||
|      * holder stream anymore. It will soon be marked as completed. | ||||
|      * @param holder holder | ||||
|      */ | ||||
|     @SuppressWarnings("unused") | ||||
|     protected void onAbort(@NonNull ActionHolder holder) { | ||||
|         // Overrideable
 | ||||
|     } | ||||
| 
 | ||||
|     @CallSuper | ||||
|     @Override | ||||
|     public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { | ||||
|         if (needsOnStart) { | ||||
|             onStart(holder); | ||||
|             needsOnStart = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureProgressed(@NonNull ActionHolder holder, | ||||
|                                     @NonNull CaptureRequest request, | ||||
|                                     @NonNull CaptureResult result) { | ||||
|         // Overrideable
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         // Overrideable
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called by subclasses to notify of their state. If state is {@link #STATE_COMPLETED}, | ||||
|      * this removes this action from the holder. | ||||
|      * @param newState new state | ||||
|      */ | ||||
|     protected final void setState(int newState) { | ||||
|         if (newState != state) { | ||||
|             state = newState; | ||||
|             for (ActionCallback callback : callbacks) { | ||||
|                 callback.onActionStateChanged(this, state); | ||||
|             } | ||||
|             if (state == STATE_COMPLETED) { | ||||
|                 holder.removeAction(this); | ||||
|                 onCompleted(holder); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether this action has reached the completed state. | ||||
|      * @return true if completed | ||||
|      */ | ||||
|     public boolean isCompleted() { | ||||
|         return state == STATE_COMPLETED; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when this action has completed (possibly aborted). | ||||
|      * @param holder holder | ||||
|      */ | ||||
|     protected void onCompleted(@NonNull ActionHolder holder) { | ||||
|         // Overrideable
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the holder. | ||||
|      * @return the holder | ||||
|      */ | ||||
|     @NonNull | ||||
|     protected ActionHolder getHolder() { | ||||
|         return holder; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Reads a characteristic with a fallback. | ||||
|      * @param key key | ||||
|      * @param fallback fallback | ||||
|      * @param <T> key type | ||||
|      * @return value or fallback | ||||
|      */ | ||||
|     @NonNull | ||||
|     protected <T> T readCharacteristic(@NonNull CameraCharacteristics.Key<T> key, | ||||
|                                        @NonNull T fallback) { | ||||
|         T value = holder.getCharacteristics(this).get(key); | ||||
|         return value == null ? fallback : value; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void addCallback(@NonNull ActionCallback callback) { | ||||
|         if (!callbacks.contains(callback)) { | ||||
|             callbacks.add(callback); | ||||
|             callback.onActionStateChanged(this, getState()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void removeCallback(@NonNull ActionCallback callback) { | ||||
|         callbacks.remove(callback); | ||||
|     } | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * A special {@link ActionCallback} that just checks for the | ||||
|  * completed state. Handy as an inner anonymous class. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public abstract class CompletionCallback implements ActionCallback { | ||||
| 
 | ||||
|     @Override | ||||
|     public final void onActionStateChanged(@NonNull Action action, int state) { | ||||
|         if (state == Action.STATE_COMPLETED) { | ||||
|             onActionCompleted(action); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The given action has just reached the completed state. | ||||
|      * @param action action | ||||
|      */ | ||||
|     protected abstract void onActionCompleted(@NonNull Action action); | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.engine.CameraEngine; | ||||
| 
 | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| public class LogAction extends BaseAction { | ||||
| 
 | ||||
|     private final static CameraLogger LOG | ||||
|             = CameraLogger.create(CameraEngine.class.getSimpleName()); | ||||
| 
 | ||||
|     private String lastLog; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         super.onCaptureCompleted(holder, request, result); | ||||
|         Integer aeMode = result.get(CaptureResult.CONTROL_AE_MODE); | ||||
|         Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); | ||||
|         Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); | ||||
|         Boolean aeLock = result.get(CaptureResult.CONTROL_AE_LOCK); | ||||
|         Integer aeTriggerState = result.get(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER); | ||||
|         Integer afTriggerState = result.get(CaptureResult.CONTROL_AF_TRIGGER); | ||||
|         String log = "aeMode: " + aeMode + " aeLock: " + aeLock + | ||||
|                 " aeState: " + aeState + " aeTriggerState: " + aeTriggerState + | ||||
|                 " afState: " + afState + " afTriggerState: " + afTriggerState; | ||||
|         if (!log.equals(lastLog)) { | ||||
|             lastLog = log; | ||||
|             LOG.i(log); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCompleted(@NonNull ActionHolder holder) { | ||||
|         super.onCompleted(holder); | ||||
|         setState(0); // set another state.
 | ||||
|         start(holder); // restart.
 | ||||
|     } | ||||
| } | ||||
| @ -1,96 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * Executes a list of actions in sequence, completing once | ||||
|  * the last of them has been completed. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| class SequenceAction extends BaseAction { | ||||
|     // Need to be BaseAction so we can call onStart() instead of start()
 | ||||
|     private final List<BaseAction> actions; | ||||
|     private int runningAction = -1; | ||||
| 
 | ||||
|     SequenceAction(@NonNull List<BaseAction> actions) { | ||||
|         this.actions = actions; | ||||
|         increaseRunningAction(); | ||||
|     } | ||||
| 
 | ||||
|     private void increaseRunningAction() { | ||||
|         boolean first = runningAction == -1; | ||||
|         boolean last = runningAction == actions.size() - 1; | ||||
|         if (last) { | ||||
|             // This was the last action. We're done.
 | ||||
|             setState(STATE_COMPLETED); | ||||
|         } else { | ||||
|             runningAction++; | ||||
|             actions.get(runningAction).addCallback(new ActionCallback() { | ||||
|                 @Override | ||||
|                 public void onActionStateChanged(@NonNull Action action, int state) { | ||||
|                     if (state == STATE_COMPLETED) { | ||||
|                         action.removeCallback(this); | ||||
|                         increaseRunningAction(); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|             if (!first) { | ||||
|                 actions.get(runningAction).onStart(getHolder()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart(@NonNull ActionHolder holder) { | ||||
|         super.onStart(holder); | ||||
|         if (runningAction >= 0) { | ||||
|             actions.get(runningAction).onStart(holder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onAbort(@NonNull ActionHolder holder) { | ||||
|         super.onAbort(holder); | ||||
|         if (runningAction >= 0) { | ||||
|             // Previous actions have been completed already.
 | ||||
|             // Future actions will never start. So this is OK.
 | ||||
|             actions.get(runningAction).onAbort(holder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { | ||||
|         super.onCaptureStarted(holder, request); | ||||
|         if (runningAction >= 0) { | ||||
|             actions.get(runningAction).onCaptureStarted(holder, request); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureProgressed(@NonNull ActionHolder holder, | ||||
|                                     @NonNull CaptureRequest request, | ||||
|                                     @NonNull CaptureResult result) { | ||||
|         super.onCaptureProgressed(holder, request, result); | ||||
|         if (runningAction >= 0) { | ||||
|             actions.get(runningAction).onCaptureProgressed(holder, request, result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         super.onCaptureCompleted(holder, request, result); | ||||
|         if (runningAction >= 0) { | ||||
|             actions.get(runningAction).onCaptureCompleted(holder, request, result); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| /** | ||||
|  * An {@link Action} that wraps another, and forces the completion | ||||
|  * after the given timeout in milliseconds is reached. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| class TimeoutAction extends ActionWrapper { | ||||
| 
 | ||||
|     private long startMillis; | ||||
|     private long timeoutMillis; | ||||
|     private BaseAction action; | ||||
| 
 | ||||
|     TimeoutAction(long timeoutMillis, @NonNull BaseAction action) { | ||||
|         this.timeoutMillis = timeoutMillis; | ||||
|         this.action = action; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public BaseAction getAction() { | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart(@NonNull ActionHolder holder) { | ||||
|         startMillis = System.currentTimeMillis(); | ||||
|         super.onStart(holder); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         super.onCaptureCompleted(holder, request, result); | ||||
|         if (!isCompleted()) { | ||||
|             if (System.currentTimeMillis() > startMillis + timeoutMillis) { | ||||
|                 // This will set our state to COMPLETED and stop requests.
 | ||||
|                 getAction().abort(holder); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,86 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.engine.action; | ||||
| 
 | ||||
| import android.hardware.camera2.CaptureRequest; | ||||
| import android.hardware.camera2.CaptureResult; | ||||
| import android.hardware.camera2.TotalCaptureResult; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * Performs a list of actions together, completing | ||||
|  * once all of them have completed. | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| class TogetherAction extends BaseAction { | ||||
|     // Need to be BaseAction so we can call onStart() instead of start()
 | ||||
|     private final List<BaseAction> actions; | ||||
|     private final List<BaseAction> runningActions; | ||||
| 
 | ||||
|     TogetherAction(@NonNull final List<BaseAction> actions) { | ||||
|         this.actions = new ArrayList<>(actions); | ||||
|         this.runningActions = new ArrayList<>(actions); | ||||
|         for (BaseAction action : actions) { | ||||
|             action.addCallback(new ActionCallback() { | ||||
|                 @Override | ||||
|                 public void onActionStateChanged(@NonNull Action action, int state) { | ||||
|                     if (state == STATE_COMPLETED) { | ||||
|                         //noinspection SuspiciousMethodCalls
 | ||||
|                         runningActions.remove(action); | ||||
|                     } | ||||
|                     if (runningActions.isEmpty()) { | ||||
|                         setState(STATE_COMPLETED); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart(@NonNull ActionHolder holder) { | ||||
|         super.onStart(holder); | ||||
|         for (BaseAction action : actions) { | ||||
|             if (!action.isCompleted()) action.onStart(holder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onAbort(@NonNull ActionHolder holder) { | ||||
|         super.onAbort(holder); | ||||
|         for (BaseAction action : actions) { | ||||
|             if (!action.isCompleted()) action.onAbort(holder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { | ||||
|         super.onCaptureStarted(holder, request); | ||||
|         for (BaseAction action : actions) { | ||||
|             if (!action.isCompleted()) action.onCaptureStarted(holder, request); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureProgressed(@NonNull ActionHolder holder, | ||||
|                                     @NonNull CaptureRequest request, | ||||
|                                     @NonNull CaptureResult result) { | ||||
|         super.onCaptureProgressed(holder, request, result); | ||||
|         for (BaseAction action : actions) { | ||||
|             if (!action.isCompleted()) action.onCaptureProgressed(holder, request, result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCaptureCompleted(@NonNull ActionHolder holder, | ||||
|                                    @NonNull CaptureRequest request, | ||||
|                                    @NonNull TotalCaptureResult result) { | ||||
|         super.onCaptureCompleted(holder, request, result); | ||||
|         for (BaseAction action : actions) { | ||||
|             if (!action.isCompleted()) action.onCaptureCompleted(holder, request, result); | ||||
|         } | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue