├── .gitignore ├── LICENSE ├── README.md ├── RELEASE.md ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle └── src │ └── main │ └── groovy │ └── com │ └── infinum │ └── maven │ ├── SonatypeConfiguration.groovy │ └── shared │ └── Configuration.groovy ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── co │ │ └── infinum │ │ └── example │ │ ├── Extensions.kt │ │ ├── ListItem.kt │ │ ├── ListItemAdapter.kt │ │ ├── MainActivity.kt │ │ ├── SettingsAdapter.kt │ │ └── SettingsItem.kt │ └── res │ ├── drawable-mdpi │ └── ic_logo.png │ ├── drawable-xhdpi │ └── ic_logo.png │ ├── drawable-xxhdpi │ └── ic_logo.png │ ├── drawable │ ├── ic_arrow_down.xml │ ├── ic_arrow_up.xml │ ├── ic_record_video.xml │ ├── ic_settings.xml │ ├── ic_stop.xml │ ├── ic_switch_camera.xml │ └── ic_take_picture.xml │ ├── layout │ ├── activity_main.xml │ ├── header_settings.xml │ ├── item_list.xml │ ├── item_settings.xml │ └── list.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── goldeneye ├── build.gradle ├── proguard-rules.pro ├── publish.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── co │ └── infinum │ └── goldeneye │ ├── BaseGoldenEye.kt │ ├── Callbacks.kt │ ├── Exceptions.kt │ ├── GoldenEye.kt │ ├── GoldenEye1Impl.kt │ ├── GoldenEye2Impl.kt │ ├── Logger.kt │ ├── PictureTransformation.kt │ ├── config │ ├── AdvancedFeatureConfig.kt │ ├── BasicFeatureConfig.kt │ ├── CameraConfig.kt │ ├── CameraInfo.kt │ ├── SizeConfig.kt │ ├── VideoConfig.kt │ ├── ZoomConfig.kt │ ├── camera1 │ │ ├── AdvancedFeatureConfigImpl.kt │ │ ├── BasicFeatureConfigImpl.kt │ │ ├── Camera1ConfigImpl.kt │ │ ├── ConfigUpdateHandler.kt │ │ ├── SizeConfigImpl.kt │ │ ├── VideoConfigImpl.kt │ │ └── ZoomConfigImpl.kt │ └── camera2 │ │ ├── AdvancedFeatureConfigImpl.kt │ │ ├── BasicFeatureConfig.kt │ │ ├── Camera2ConfigImpl.kt │ │ ├── ConfigUpdateHandler.kt │ │ ├── SizeConfigImpl.kt │ │ ├── VideoConfigImpl.kt │ │ └── ZoomConfigImpl.kt │ ├── extensions │ ├── Any.kt │ ├── Bitmap.kt │ ├── Camera.kt │ ├── CaptureRequest.Builder.kt │ ├── CaptureResult.kt │ ├── Context.kt │ ├── Matrix.kt │ ├── MediaRecorder.kt │ ├── TextureView.kt │ └── View.kt │ ├── gesture │ ├── FocusHandler.kt │ ├── GestureManager.kt │ ├── ZoomHandler.kt │ ├── ZoomHandlerImpl.kt │ ├── camera1 │ │ └── FocusHandlerImpl.kt │ └── camera2 │ │ └── FocusHandlerImpl.kt │ ├── models │ ├── AntibandingMode.kt │ ├── Camera2Error.kt │ ├── CameraApi.kt │ ├── CameraProperty.kt │ ├── CameraRequest.kt │ ├── CameraState.kt │ ├── ColorEffectMode.kt │ ├── Facing.kt │ ├── FlashMode.kt │ ├── FocusMode.kt │ ├── PreviewScale.kt │ ├── Size.kt │ ├── VideoQuality.kt │ └── WhiteBalanceMode.kt │ ├── recorders │ ├── PictureRecorder.kt │ └── VideoRecorder.kt │ ├── sessions │ ├── BaseSession.kt │ ├── PictureSession.kt │ ├── SessionsManager.kt │ └── VideoSession.kt │ └── utils │ ├── AsyncUtils.kt │ ├── CameraUtils.kt │ ├── IncompatibleDevicesUtils.kt │ ├── Intrinsics.kt │ └── LogDelegate.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── logo.svg ├── maven.gradle ├── settings.gradle └── tasks.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,macos,linux,gradle,android,intellij,androidstudio 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | .idea/caches 48 | 49 | # Keystore files 50 | # Uncomment the following line if you do not want to check your keystore files in. 51 | #*.jks 52 | 53 | # External native build folder generated in Android Studio 2.2 and later 54 | .externalNativeBuild 55 | 56 | # Google Services (e.g. APIs or Firebase) 57 | google-services.json 58 | 59 | # Freeline 60 | freeline.py 61 | freeline/ 62 | freeline_project_description.json 63 | 64 | # fastlane 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | fastlane/readme.md 70 | 71 | ### Android Patch ### 72 | gen-external-apklibs 73 | 74 | ### AndroidStudio ### 75 | # Covers files to be ignored for android development using Android Studio. 76 | 77 | # Built application files 78 | 79 | # Files for the ART/Dalvik VM 80 | 81 | # Java class files 82 | 83 | # Generated files 84 | 85 | # Gradle files 86 | .gradle 87 | 88 | # Signing files 89 | .signing/ 90 | 91 | # Local configuration file (sdk path, etc) 92 | 93 | # Proguard folder generated by Eclipse 94 | 95 | # Log Files 96 | 97 | # Android Studio 98 | /*/build/ 99 | /*/local.properties 100 | /*/out 101 | /*/*/build 102 | /*/*/production 103 | *.ipr 104 | *~ 105 | *.swp 106 | 107 | # Android Patch 108 | 109 | # External native build folder generated in Android Studio 2.2 and later 110 | 111 | # NDK 112 | obj/ 113 | 114 | # IntelliJ IDEA 115 | *.iws 116 | /out/ 117 | 118 | # User-specific configurations 119 | .idea/caches/ 120 | .idea/libraries/ 121 | .idea/shelf/ 122 | .idea/.name 123 | .idea/compiler.xml 124 | .idea/copyright/profiles_settings.xml 125 | .idea/encodings.xml 126 | .idea/misc.xml 127 | .idea/modules.xml 128 | .idea/scopes/scope_settings.xml 129 | .idea/vcs.xml 130 | .idea/jsLibraryMappings.xml 131 | .idea/datasources.xml 132 | .idea/dataSources.ids 133 | .idea/sqlDataSources.xml 134 | .idea/dynamic.xml 135 | .idea/uiDesigner.xml 136 | 137 | # OS-specific files 138 | .DS_Store 139 | .DS_Store? 140 | ._* 141 | .Spotlight-V100 142 | .Trashes 143 | ehthumbs.db 144 | Thumbs.db 145 | 146 | # Legacy Eclipse project files 147 | .classpath 148 | .project 149 | .cproject 150 | .settings/ 151 | 152 | # Mobile Tools for Java (J2ME) 153 | .mtj.tmp/ 154 | 155 | # Package Files # 156 | *.war 157 | *.ear 158 | 159 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 160 | hs_err_pid* 161 | 162 | ## Plugin-specific files: 163 | 164 | # mpeltonen/sbt-idea plugin 165 | .idea_modules/ 166 | 167 | # JIRA plugin 168 | atlassian-ide-plugin.xml 169 | 170 | # Mongo Explorer plugin 171 | .idea/mongoSettings.xml 172 | 173 | # Crashlytics plugin (for Android Studio and IntelliJ) 174 | com_crashlytics_export_strings.xml 175 | crashlytics.properties 176 | crashlytics-build.properties 177 | fabric.properties 178 | 179 | ### AndroidStudio Patch ### 180 | 181 | !/gradle/wrapper/gradle-wrapper.jar 182 | 183 | ### Intellij ### 184 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 185 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 186 | 187 | # User-specific stuff 188 | .idea/**/workspace.xml 189 | .idea/**/tasks.xml 190 | .idea/**/usage.statistics.xml 191 | .idea/**/dictionaries 192 | .idea/**/shelf 193 | 194 | # Sensitive or high-churn files 195 | .idea/**/dataSources/ 196 | .idea/**/dataSources.ids 197 | .idea/**/dataSources.local.xml 198 | .idea/**/sqlDataSources.xml 199 | .idea/**/dynamic.xml 200 | .idea/**/uiDesigner.xml 201 | .idea/**/dbnavigator.xml 202 | 203 | # Gradle 204 | .idea/**/gradle.xml 205 | .idea/**/libraries 206 | 207 | # Gradle and Maven with auto-import 208 | # When using Gradle or Maven with auto-import, you should exclude module files, 209 | # since they will be recreated, and may cause churn. Uncomment if using 210 | # auto-import. 211 | # .idea/modules.xml 212 | # .idea/*.iml 213 | # .idea/modules 214 | 215 | # CMake 216 | cmake-build-*/ 217 | 218 | # Mongo Explorer plugin 219 | .idea/**/mongoSettings.xml 220 | 221 | # File-based project format 222 | 223 | # IntelliJ 224 | 225 | # mpeltonen/sbt-idea plugin 226 | 227 | # JIRA plugin 228 | 229 | # Cursive Clojure plugin 230 | .idea/replstate.xml 231 | 232 | # Crashlytics plugin (for Android Studio and IntelliJ) 233 | 234 | # Editor-based Rest Client 235 | .idea/httpRequests 236 | 237 | ### Intellij Patch ### 238 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 239 | 240 | # *.iml 241 | # modules.xml 242 | # .idea/misc.xml 243 | # *.ipr 244 | 245 | # Sonarlint plugin 246 | .idea/sonarlint 247 | 248 | ### Java ### 249 | # Compiled class file 250 | 251 | # Log file 252 | 253 | # BlueJ files 254 | *.ctxt 255 | 256 | # Mobile Tools for Java (J2ME) 257 | 258 | # Package Files # 259 | *.jar 260 | *.nar 261 | *.zip 262 | *.tar.gz 263 | *.rar 264 | 265 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 266 | 267 | ### Linux ### 268 | 269 | # temporary files which can be created if a process still has a handle open of a deleted file 270 | .fuse_hidden* 271 | 272 | # KDE directory preferences 273 | .directory 274 | 275 | # Linux trash folder which might appear on any partition or disk 276 | .Trash-* 277 | 278 | # .nfs files are created when an open file is removed but is still being accessed 279 | .nfs* 280 | 281 | ### macOS ### 282 | # General 283 | .AppleDouble 284 | .LSOverride 285 | 286 | # Icon must end with two \r 287 | Icon 288 | 289 | # Thumbnails 290 | 291 | # Files that might appear in the root of a volume 292 | .DocumentRevisions-V100 293 | .fseventsd 294 | .TemporaryItems 295 | .VolumeIcon.icns 296 | .com.apple.timemachine.donotpresent 297 | 298 | # Directories potentially created on remote AFP share 299 | .AppleDB 300 | .AppleDesktop 301 | Network Trash Folder 302 | Temporary Items 303 | .apdisk 304 | 305 | ### Gradle ### 306 | /build/ 307 | 308 | # Ignore Gradle GUI config 309 | gradle-app.setting 310 | 311 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 312 | !gradle-wrapper.jar 313 | 314 | # Cache of project 315 | .gradletasknamecache 316 | 317 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 318 | # gradle/wrapper/gradle-wrapper.properties 319 | 320 | 321 | # End of https://www.gitignore.io/api/java,macos,linux,gradle,android,intellij,androidstudio 322 | 323 | .idea/ 324 | 325 | # Maven central publishing ignores 326 | publish.properties 327 | *.gpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATION NOTICE 2 | 3 | This library is no longer being maintained. Since it is still being actively used on many projects, we will consider fixing any potential critical bugs, but there will be no further feature development nor minor bug fixing. 4 | 5 | We recommend migrating to [CameraX](https://developer.android.com/training/camerax). While some very simple use-cases are still easier with GoldenEye, CameraX is lifecycle-aware and is an overall more complete solution. 6 | 7 | # GoldenEye 8 | 9 | 10 | 11 | ## Quick guide 12 | 13 | #### Add dependency 14 | 15 | ```gradle 16 | implementation 'co.infinum:goldeneye:1.1.2' 17 | ``` 18 | 19 | #### Initialize 20 | 21 | ```kotlin 22 | val goldenEye = GoldenEye.Builder(activity).build() 23 | ``` 24 | 25 | #### Open camera 26 | 27 | ```kotlin 28 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) 29 | == PackageManager.PERMISSION_GRANTED 30 | ) { 31 | /* Find back camera */ 32 | val backCamera = goldenEye.availableCameras.find { it.facing == Facing.BACK } 33 | /* Open back camera */ 34 | goldenEye.open(textureView, backCamera, initCallback) 35 | } 36 | ``` 37 | 38 | #### Take picture 39 | 40 | ```kotlin 41 | goldenEye.takePicture(pictureCallback) 42 | ``` 43 | 44 | #### Record video 45 | 46 | ```kotlin 47 | goldenEye.startRecording(file, videoCallback) 48 | /* Somewhere later */ 49 | goldenEye.stopRecording() 50 | ``` 51 | 52 | You can see all GoldenEye methods [here](./goldeneye/src/main/java/co/infinum/goldeneye/GoldenEye.kt). 53 | 54 | ## Features 55 | 56 | GoldenEye supports multiple Camera features that can be changed at runtime: 57 | 58 | - [Flash mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/FlashMode.kt) 59 | - [Focus mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/FocusMode.kt) 60 | - [Preview scale](./goldeneye/src/main/java/co/infinum/goldeneye/models/PreviewScale.kt) 61 | - Preview size 62 | - Picture size 63 | - Picture quality 64 | - [Video quality](./goldeneye/src/main/java/co/infinum/goldeneye/models/VideoQuality.kt) 65 | - Tap to focus 66 | - Pinch to zoom 67 | - [Antibanding mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/AntibandingMode.kt) 68 | - [White balance mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/WhiteBalanceMode.kt) 69 | - [Color effect mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/ColorEffectMode.kt) 70 | 71 | If you are interested to read in detail what is supported, there is thorough documentation inside [interfaces](./goldeneye/src/main/java/co/infinum/goldeneye/config). 72 | 73 | ## Builder 74 | 75 | When initializing GoldenEye instance you can configure it to fit your needs with several interfaces. 76 | 77 | #### [Logger](./goldeneye/src/main/java/co/infinum/goldeneye/Logger.kt) 78 | 79 | By default logging is turned **OFF**. By implementing Logger interface, you can enable logs if needed. 80 | 81 | ```kotlin 82 | object: Logger { 83 | override fun log(message: String) { 84 | /* Log standard message */ 85 | } 86 | 87 | override fun log(t: Throwable) { 88 | /* Log error */ 89 | } 90 | } 91 | ``` 92 | 93 | #### [OnZoomChangedCallback](./goldeneye/src/main/java/co/infinum/goldeneye/Callbacks.kt) 94 | 95 | GoldenEye supports pinch to zoom functionality. By using OnZoomChangedCallback you can receive callback every time the zoom changes. 96 | 97 | ```kotlin 98 | object: OnZoomChangedCallback { 99 | override fun onZoomChanged(zoom: Int) { 100 | /* Do something */ 101 | } 102 | } 103 | ``` 104 | 105 | #### [OnFocusChangedCallback](./goldeneye/src/main/java/co/infinum/goldeneye/Callbacks.kt) 106 | 107 | GoldenEye supports tap to focus functionality. By using OnFocusChangedCallback you can receive callback every time the focus changes. 108 | 109 | ```kotlin 110 | object: OnFocusChangedCallback { 111 | override fun onFocusChanged(point: Point) { 112 | /* Do something */ 113 | } 114 | } 115 | ``` 116 | 117 | #### [PictureTransformation](./goldeneye/src/main/java/co/infinum/goldeneye/PictureTransformation.kt) 118 | 119 | Once the picture is taken, by default, library will rotate the bitmap to be in sync with device's orientation and mirror 120 | the image if it is taken with front camera. If you are not OK with this behavior, you can provide `PictureTransformation` implementation 121 | that will be used instead. `PictureTransformation.transform` method is executed on the **background** thread! 122 | 123 | ```kotlin 124 | object: PictureTransformation { 125 | override fun transform(picture: Bitmap, config: CameraConfig, orientationDifference: Float): Bitmap { 126 | /* Transform raw picture */ 127 | } 128 | } 129 | ``` 130 | 131 | #### Advanced features 132 | 133 | - [Antibanding mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/AntibandingMode.kt) 134 | - [White balance mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/WhiteBalanceMode.kt) 135 | - [Color effect mode](./goldeneye/src/main/java/co/infinum/goldeneye/models/ColorEffectMode.kt) 136 | 137 | Advanced features are still in experimental phase and we noticed that they do not work on some devices and they were not 138 | thoroughly tested so we decided to disable them by default. That means that if you try to change the value via setter, it will simply be ignored. 139 | 140 | In case you want to try and play with advanced features, you can enable them when initializing GoldenEye instance. 141 | 142 | ```kotlin 143 | GoldenEye.Builder(activity) 144 | .withAdvancedFeatures() 145 | .build() 146 | ``` 147 | 148 | #### Manually set Camera API 149 | 150 | You can manually set Camera API and override default GoldenEye behavior. You can call `GoldenEye.preferredCameraApi(Context)` to check which 151 | Camera API will be used by default. It can be useful to force Camera1 API as it is more consistent when taking pictures with FlashMode.ON 152 | than Camera2. The issue with Camera1 is that some newer devices would **crash** when trying to record a video so be very cautious. 153 | 154 | ```kotlin 155 | GoldenEye.Builder(activity) 156 | .setCameraApi(CameraApi.CAMERA1) 157 | .build() 158 | ``` 159 | 160 | ## Edge case behavior 161 | 162 | - If you call `startRecording` or `takePicture` while GoldenEye is already taking a picture or recording a video, immediate `onError` 163 | callback will be dispatched for the **second** call, first call to `startRecording` or `takePicture` will still be active 164 | - If you call `release` while GoldenEye is taking a picture or recording a video, everything will be canceled including all callbacks, 165 | nothing will be dispatched 166 | - If you call `GoldenEye.config` before `InitCallback#onReady` is dispatched, returned `config` will be `null` 167 | - If you call `open` while camera is already opened, old camera will be released and closed and new camera will be opened 168 | 169 | ## Known issues 170 | 171 | - Video recording with external camera is not supported due to current video configuration limitations of the internal API design 172 | - OnePlus 6 - ColorEffectMode does not work 173 | - Huawei Nexus 6P - Picture taken with Flash is too dark 174 | - LG G5 - Picture taken with Flash is too dark 175 | 176 | ## Contributing 177 | 178 | Feedback and code contributions are very much welcome. Just make a pull request with a short description of your changes. 179 | By making contributions to this project you give permission for your code to be used under the same [license](LICENSE). 180 | 181 | ## Credits 182 | 183 | Maintained and sponsored by [Infinum](http://www.infinum.co). 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Release guide 2 | 3 | ### Define environment variables 4 | 5 | You must define **BINTRAY_USER** and **BINTRAY_API_KEY** as environment variable. 6 | 7 | Bintray API key can be found under Edit Profile -> API Key. 8 | 9 | ### Check README.md and build.gradle 10 | 11 | Be sure that [build.gradle](https://github.com/infinum/Android-GoldenEye/blob/master/build.gradle) and [Readme](https://github.com/infinum/Android-Goldfinger/blob/master/README.md) are both updated and contain new, to be released version. 12 | 13 | ### Run gradle task 14 | 15 | `./gradlew clean build javadocs assemble` 16 | `./gradlew bintrayUpload` 17 | 18 | ### Bintray publish 19 | 20 | Manually go to [Bintray page](https://bintray.com/infinum/android), check that all 8 files are there and publish them if everything is OK. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | apply from: "maven.gradle" 4 | 5 | ext.sdk = [ 6 | min : 14, 7 | target: 28 8 | ] 9 | 10 | ext.versions = [ 11 | goldeneye: '1.1.2', 12 | google : '28.0.0', 13 | kotlin : '1.4.32' 14 | ] 15 | 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | dependencies { 21 | classpath 'com.android.tools.build:gradle:4.1.3' 22 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" 23 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.20" 24 | } 25 | } 26 | 27 | allprojects { 28 | repositories { 29 | google() 30 | jcenter() 31 | } 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "groovy" 3 | } -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/com/infinum/maven/SonatypeConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package com.infinum.maven 2 | 3 | import com.infinum.maven.shared.Configuration 4 | 5 | class SonatypeConfiguration implements Configuration { 6 | 7 | private Properties properties = new Properties() 8 | 9 | @Override 10 | void load() { 11 | File file = new File("publish.properties") 12 | if (file.exists()) { 13 | properties.load(new FileInputStream(file)) 14 | } else { 15 | properties.setProperty("sonatype.name", "") 16 | properties.setProperty("sonatype.url", "") 17 | properties.setProperty("sonatype.user", "") 18 | properties.setProperty("sonatype.password", "") 19 | } 20 | } 21 | 22 | @Override 23 | String name() { 24 | return properties.getProperty("sonatype.name").toString() 25 | } 26 | 27 | @Override 28 | String url() { 29 | return properties.getProperty("sonatype.url").toString() 30 | } 31 | 32 | @Override 33 | String username() { 34 | return properties.getProperty("sonatype.user").toString() 35 | } 36 | 37 | @Override 38 | String password() { 39 | return properties.getProperty("sonatype.password").toString() 40 | } 41 | } -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/com/infinum/maven/shared/Configuration.groovy: -------------------------------------------------------------------------------- 1 | package com.infinum.maven.shared 2 | 3 | interface Configuration { 4 | 5 | void load() 6 | 7 | String name() 8 | 9 | String url() 10 | 11 | String username() 12 | 13 | String password() 14 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion sdk.target 7 | 8 | defaultConfig { 9 | applicationId "co.infinum.example" 10 | minSdkVersion sdk.min 11 | targetSdkVersion sdk.target 12 | } 13 | 14 | defaultConfig.vectorDrawables.useSupportLibrary = true 15 | } 16 | 17 | dependencies { 18 | implementation project(":goldeneye") 19 | implementation "com.android.support:appcompat-v7:$versions.google" 20 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" 21 | implementation "com.android.support:recyclerview-v7:$versions.google" 22 | } -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/Extensions.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | import android.content.Context 4 | import android.support.v7.app.AlertDialog 5 | import android.support.v7.widget.LinearLayoutManager 6 | import android.support.v7.widget.RecyclerView 7 | import android.widget.Toast 8 | import co.infinum.goldeneye.config.CameraConfig 9 | import co.infinum.goldeneye.models.* 10 | 11 | fun FocusMode.convertToString() = name.toLowerCase() 12 | fun FlashMode.convertToString() = name.toLowerCase() 13 | fun PreviewScale.convertToString() = name.toLowerCase() 14 | fun WhiteBalanceMode.convertToString() = name.toLowerCase() 15 | fun Size.convertToString() = "$width x $height" 16 | fun Int.toPercentage() = "%.02fx".format(this / 100f) 17 | fun Boolean.convertToString() = if (this) "Enabled" else "Disabled" 18 | fun AntibandingMode.convertToString() = name.toLowerCase() 19 | fun ColorEffectMode.convertToString() = name.toLowerCase() 20 | fun VideoQuality.convertToString() = name.toLowerCase() 21 | fun boolList() = listOf( 22 | ListItem(true, "Enabled"), 23 | ListItem(false, "Disabled") 24 | ) 25 | 26 | fun Context.toast(text: String) = Toast.makeText(this, text, Toast.LENGTH_LONG).show() 27 | 28 | fun CameraConfig.prepareItems(context: Context, adapter: SettingsAdapter) { 29 | val settingsItems = listOf( 30 | SettingsItem(name = "Basic features", type = 1), 31 | SettingsItem("Preview size:", previewSize.convertToString()) { 32 | if (previewScale == PreviewScale.MANUAL 33 | || previewScale == PreviewScale.MANUAL_FIT 34 | || previewScale == PreviewScale.MANUAL_FILL 35 | ) { 36 | displayDialog( 37 | context = context, 38 | config = this, 39 | settingsAdapter = adapter, 40 | title = "Preview size", 41 | listItems = supportedPreviewSizes.map { ListItem(it, it.convertToString()) }, 42 | onClick = { previewSize = it } 43 | ) 44 | } else { 45 | context.toast("Preview scale is automatic so it picks preview size on its own.") 46 | } 47 | }, 48 | SettingsItem("Picture size:", pictureSize.convertToString()) { 49 | displayDialog( 50 | context = context, 51 | config = this, 52 | settingsAdapter = adapter, 53 | title = "Picture size", 54 | listItems = supportedPictureSizes.map { ListItem(it, it.convertToString()) }, 55 | onClick = { pictureSize = it } 56 | ) 57 | }, 58 | SettingsItem("Preview scale", previewScale.convertToString()) { 59 | displayDialog( 60 | context = context, 61 | config = this, 62 | settingsAdapter = adapter, 63 | title = "Preview scale", 64 | listItems = PreviewScale.values().map { ListItem(it, it.convertToString()) }, 65 | onClick = { previewScale = it } 66 | ) 67 | }, 68 | SettingsItem("Video quality:", videoQuality.convertToString()) { 69 | displayDialog( 70 | context = context, 71 | config = this, 72 | settingsAdapter = adapter, 73 | title = "Video quality", 74 | listItems = supportedVideoQualities.map { ListItem(it, it.convertToString()) }, 75 | onClick = { videoQuality = it } 76 | ) 77 | }, 78 | SettingsItem("Video stabilization:", videoStabilizationEnabled.convertToString()) { 79 | if (isVideoStabilizationSupported) { 80 | displayDialog( 81 | context = context, 82 | config = this, 83 | settingsAdapter = adapter, 84 | title = "Video stabilization", 85 | listItems = boolList(), 86 | onClick = { videoStabilizationEnabled = it } 87 | ) 88 | } else { 89 | context.toast("Video stabilization not supported") 90 | } 91 | }, 92 | SettingsItem("Flash mode:", flashMode.convertToString()) { 93 | displayDialog( 94 | context = context, 95 | config = this, 96 | settingsAdapter = adapter, 97 | title = "Flash mode", 98 | listItems = supportedFlashModes.map { ListItem(it, it.convertToString()) }, 99 | onClick = { flashMode = it } 100 | ) 101 | }, 102 | SettingsItem("Focus mode:", focusMode.convertToString()) { 103 | displayDialog( 104 | context = context, 105 | config = this, 106 | settingsAdapter = adapter, 107 | title = "Focus mode", 108 | listItems = supportedFocusModes.map { ListItem(it, it.convertToString()) }, 109 | onClick = { focusMode = it } 110 | ) 111 | }, 112 | SettingsItem("Tap to focus:", tapToFocusEnabled.convertToString()) { 113 | if (isTapToFocusSupported) { 114 | displayDialog( 115 | context = context, 116 | config = this, 117 | settingsAdapter = adapter, 118 | title = "Tap to focus", 119 | listItems = boolList(), 120 | onClick = { tapToFocusEnabled = it } 121 | ) 122 | } else { 123 | context.toast("Tap to focus not supported.") 124 | } 125 | }, 126 | SettingsItem("Tap to focus - reset focus delay:", tapToFocusResetDelay.toString()) { 127 | if (isTapToFocusSupported) { 128 | displayDialog( 129 | context = context, 130 | config = this, 131 | settingsAdapter = adapter, 132 | title = "Reset delay", 133 | listItems = listOf( 134 | ListItem(2_500L, "2500"), 135 | ListItem(5_000L, "5000"), 136 | ListItem(7_500L, "7500") 137 | ), 138 | onClick = { tapToFocusResetDelay = it } 139 | ) 140 | } else { 141 | context.toast("Tap to focus not supported.") 142 | } 143 | }, 144 | SettingsItem("Pinch to zoom:", pinchToZoomEnabled.convertToString()) { 145 | if (isZoomSupported) { 146 | displayDialog( 147 | context = context, 148 | config = this, 149 | settingsAdapter = adapter, 150 | title = "Pinch to zoom", 151 | listItems = boolList(), 152 | onClick = { pinchToZoomEnabled = it } 153 | ) 154 | } else { 155 | context.toast("Pinch to zoom not supported.") 156 | } 157 | }, 158 | SettingsItem("Pinch to zoom friction:", "%.02f".format(pinchToZoomFriction)) { 159 | if (isZoomSupported) { 160 | displayDialog( 161 | context = context, 162 | config = this, 163 | settingsAdapter = adapter, 164 | title = "Friction", 165 | listItems = listOf( 166 | ListItem(0.5f, "0.50"), 167 | ListItem(1f, "1.00"), 168 | ListItem(2f, "2.00") 169 | ), 170 | onClick = { pinchToZoomFriction = it } 171 | ) 172 | } else { 173 | context.toast("Pinch to zoom not supported.") 174 | } 175 | }, 176 | SettingsItem(name = "Advanced features", type = 1), 177 | SettingsItem("White Balance:", whiteBalanceMode.convertToString()) { 178 | displayDialog( 179 | context = context, 180 | config = this, 181 | settingsAdapter = adapter, 182 | title = "White Balance", 183 | listItems = supportedWhiteBalanceModes.map { ListItem(it, it.convertToString()) }, 184 | onClick = { whiteBalanceMode = it } 185 | ) 186 | }, 187 | SettingsItem("Color Effect:", colorEffectMode.convertToString()) { 188 | displayDialog( 189 | context = context, 190 | config = this, 191 | settingsAdapter = adapter, 192 | title = "Color Effect", 193 | listItems = supportedColorEffectModes.map { ListItem(it, it.convertToString()) }, 194 | onClick = { colorEffectMode = it } 195 | ) 196 | }, 197 | SettingsItem("Antibanding:", antibandingMode.convertToString()) { 198 | displayDialog( 199 | context = context, 200 | config = this, 201 | settingsAdapter = adapter, 202 | title = "Antibanding", 203 | listItems = supportedAntibandingModes.map { ListItem(it, it.convertToString()) }, 204 | onClick = { antibandingMode = it } 205 | ) 206 | } 207 | ) 208 | adapter.updateDataSet(settingsItems) 209 | } 210 | 211 | fun displayDialog(context: Context, config: CameraConfig, settingsAdapter: SettingsAdapter, 212 | title: String, listItems: List>, onClick: (T) -> Unit 213 | ) { 214 | if (listItems.isEmpty()) { 215 | context.toast("$title not supported") 216 | return 217 | } 218 | 219 | val dialog = AlertDialog.Builder(context) 220 | .setView(R.layout.list) 221 | .setCancelable(true) 222 | .setTitle(title) 223 | .show() 224 | 225 | dialog.findViewById(R.id.recyclerView)?.apply { 226 | layoutManager = LinearLayoutManager(context) 227 | adapter = ListItemAdapter(listItems) { 228 | onClick(it) 229 | dialog.dismiss() 230 | config.prepareItems(context, settingsAdapter) 231 | } 232 | } 233 | } -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/ListItem.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | data class ListItem( 4 | val realItem: T, 5 | val text: String 6 | ) -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/ListItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | 9 | class ListItemAdapter( 10 | private val items: List>, 11 | private val onClick: (T) -> Unit 12 | ) : RecyclerView.Adapter() { 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)= 14 | ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)) 15 | 16 | override fun getItemCount() = items.size 17 | 18 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 19 | val item = items[position] 20 | holder.itemView.findViewById(R.id.textView).text = item.text 21 | holder.itemView.setOnClickListener { onClick(item.realItem) } 22 | } 23 | 24 | class ViewHolder(view: View) : RecyclerView.ViewHolder(view) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.content.pm.PackageManager 6 | import android.graphics.Bitmap 7 | import android.graphics.SurfaceTexture 8 | import android.media.MediaPlayer 9 | import android.os.Bundle 10 | import android.os.Handler 11 | import android.os.Looper 12 | import android.support.v4.app.ActivityCompat 13 | import android.support.v7.app.AlertDialog 14 | import android.support.v7.app.AppCompatActivity 15 | import android.support.v7.app.AppCompatDelegate 16 | import android.support.v7.widget.LinearLayoutManager 17 | import android.util.Log 18 | import android.view.Surface 19 | import android.view.TextureView 20 | import android.view.View 21 | import co.infinum.goldeneye.GoldenEye 22 | import co.infinum.goldeneye.InitCallback 23 | import co.infinum.goldeneye.Logger 24 | import co.infinum.goldeneye.config.CameraConfig 25 | import co.infinum.goldeneye.config.CameraInfo 26 | import kotlinx.android.synthetic.main.activity_main.* 27 | import java.io.File 28 | import java.util.concurrent.Executors 29 | import kotlin.math.min 30 | 31 | @SuppressLint("SetTextI18n") 32 | class MainActivity : AppCompatActivity() { 33 | 34 | private val mainHandler = Handler(Looper.getMainLooper()) 35 | private lateinit var goldenEye: GoldenEye 36 | private lateinit var videoFile: File 37 | private var isRecording = false 38 | private var settingsAdapter = SettingsAdapter(listOf()) 39 | 40 | private val initCallback = object : InitCallback() { 41 | override fun onReady(config: CameraConfig) { 42 | zoomView.text = "Zoom: ${config.zoom.toPercentage()}" 43 | } 44 | 45 | override fun onError(t: Throwable) { 46 | t.printStackTrace() 47 | } 48 | } 49 | 50 | private val logger = object : Logger { 51 | override fun log(message: String) { 52 | Log.e("GoldenEye", message) 53 | } 54 | 55 | override fun log(t: Throwable) { 56 | t.printStackTrace() 57 | } 58 | } 59 | 60 | override fun onCreate(savedInstanceState: Bundle?) { 61 | super.onCreate(savedInstanceState) 62 | setContentView(R.layout.activity_main) 63 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 64 | initGoldenEye() 65 | videoFile = File.createTempFile("vid", "") 66 | initListeners() 67 | } 68 | 69 | private fun initListeners() { 70 | settingsView.setOnClickListener { 71 | prepareItems() 72 | settingsRecyclerView.apply { 73 | visibility = View.VISIBLE 74 | layoutManager = LinearLayoutManager(this@MainActivity) 75 | adapter = settingsAdapter 76 | } 77 | } 78 | 79 | takePictureView.setOnClickListener { _ -> 80 | goldenEye.takePicture( 81 | onPictureTaken = { bitmap -> 82 | if (bitmap.width <= 4096 && bitmap.height <= 4096) { 83 | displayPicture(bitmap) 84 | } else { 85 | reducePictureSize(bitmap) 86 | } 87 | }, 88 | onError = { it.printStackTrace() } 89 | ) 90 | } 91 | 92 | recordVideoView.setOnClickListener { _ -> 93 | if (isRecording) { 94 | isRecording = false 95 | recordVideoView.setImageResource(R.drawable.ic_record_video) 96 | goldenEye.stopRecording() 97 | } else { 98 | startRecording() 99 | } 100 | } 101 | 102 | switchCameraView.setOnClickListener { _ -> 103 | val currentIndex = goldenEye.availableCameras.indexOfFirst { goldenEye.config?.id == it.id } 104 | val nextIndex = (currentIndex + 1) % goldenEye.availableCameras.size 105 | openCamera(goldenEye.availableCameras[nextIndex]) 106 | } 107 | } 108 | 109 | private fun reducePictureSize(bitmap: Bitmap) { 110 | Executors.newSingleThreadExecutor().execute { 111 | try { 112 | val scaleX = 4096f / bitmap.width 113 | val scaleY = 4096f / bitmap.height 114 | val scale = min(scaleX, scaleY) 115 | val newBitmap = Bitmap.createScaledBitmap(bitmap, (bitmap.width * scale).toInt(), (bitmap.height * scale).toInt(), true) 116 | mainHandler.post { 117 | displayPicture(newBitmap) 118 | } 119 | } catch (t: Throwable) { 120 | toast("Picture is too big. Reduce picture size in settings below 4096x4096.") 121 | } 122 | } 123 | } 124 | 125 | private fun displayPicture(bitmap: Bitmap) { 126 | previewPictureView.apply { 127 | setImageBitmap(bitmap) 128 | visibility = View.VISIBLE 129 | } 130 | mainHandler.postDelayed( 131 | { previewPictureView.visibility = View.GONE }, 132 | 2000 133 | ) 134 | } 135 | 136 | private fun initGoldenEye() { 137 | goldenEye = GoldenEye.Builder(this) 138 | .setLogger(logger) 139 | .setOnZoomChangedCallback { zoomView.text = "Zoom: ${it.toPercentage()}" } 140 | .build() 141 | } 142 | 143 | override fun onStart() { 144 | super.onStart() 145 | if (goldenEye.availableCameras.isNotEmpty()) { 146 | openCamera(goldenEye.availableCameras[0]) 147 | } 148 | } 149 | 150 | override fun onStop() { 151 | super.onStop() 152 | goldenEye.release() 153 | } 154 | 155 | private fun openCamera(cameraInfo: CameraInfo) { 156 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { 157 | goldenEye.open(textureView, cameraInfo, initCallback) 158 | } else { 159 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 0x1) 160 | } 161 | } 162 | 163 | private fun startRecording() { 164 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { 165 | record() 166 | } else { 167 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 0x2) 168 | } 169 | } 170 | 171 | private fun record() { 172 | isRecording = true 173 | recordVideoView.setImageResource(R.drawable.ic_stop) 174 | goldenEye.startRecording( 175 | file = videoFile, 176 | onVideoRecorded = { 177 | previewVideoContainer.visibility = View.VISIBLE 178 | if (previewVideoView.isAvailable) { 179 | startVideo() 180 | } else { 181 | previewVideoView.surfaceTextureListener = object : TextureView.SurfaceTextureListener { 182 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {} 183 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {} 184 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = true 185 | 186 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { 187 | startVideo() 188 | } 189 | } 190 | } 191 | }, 192 | onError = { it.printStackTrace() } 193 | ) 194 | } 195 | 196 | @SuppressLint("MissingPermission") 197 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 198 | if (requestCode == 0x1) { 199 | if (grantResults.getOrNull(0) == PackageManager.PERMISSION_GRANTED) { 200 | goldenEye.open(textureView, goldenEye.availableCameras[0], initCallback) 201 | } else { 202 | AlertDialog.Builder(this) 203 | .setTitle("GoldenEye") 204 | .setMessage("Smartass Detected!") 205 | .setPositiveButton("I am smartass") { _, _ -> 206 | throw SmartassException 207 | } 208 | .setNegativeButton("Sorry") { _, _ -> 209 | openCamera(goldenEye.availableCameras[0]) 210 | } 211 | .setCancelable(false) 212 | .show() 213 | } 214 | } else if (requestCode == 0x2) { 215 | record() 216 | } 217 | } 218 | 219 | override fun onBackPressed() { 220 | if (settingsRecyclerView.visibility == View.VISIBLE) { 221 | settingsRecyclerView.visibility = View.GONE 222 | } else { 223 | super.onBackPressed() 224 | } 225 | } 226 | 227 | private fun prepareItems() { 228 | goldenEye.config?.apply { 229 | prepareItems(this@MainActivity, settingsAdapter) 230 | } 231 | } 232 | 233 | private fun startVideo() { 234 | MediaPlayer().apply { 235 | setSurface(Surface(previewVideoView.surfaceTexture)) 236 | setDataSource(videoFile.absolutePath) 237 | setOnCompletionListener { 238 | mainHandler.postDelayed({ 239 | previewVideoContainer.visibility = View.GONE 240 | release() 241 | }, 1500) 242 | } 243 | setOnVideoSizeChangedListener { _, width, height -> 244 | previewVideoView.apply { 245 | layoutParams = layoutParams.apply { 246 | val scaleX = previewVideoContainer.width / width.toFloat() 247 | val scaleY = previewVideoContainer.height / height.toFloat() 248 | val scale = min(scaleX, scaleY) 249 | 250 | this.width = (width * scale).toInt() 251 | this.height = (height * scale).toInt() 252 | } 253 | } 254 | } 255 | prepare() 256 | start() 257 | } 258 | } 259 | } 260 | 261 | object SmartassException : Throwable() 262 | -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/SettingsAdapter.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | import android.annotation.SuppressLint 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import kotlinx.android.synthetic.main.item_settings.view.* 10 | 11 | class SettingsAdapter( 12 | private var settingsItems: List 13 | ) : RecyclerView.Adapter() { 14 | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 16 | if (viewType == 0) { 17 | ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)) 18 | } else { 19 | HeaderViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.header_settings, parent, false)) 20 | } 21 | 22 | override fun getItemCount() = settingsItems.size 23 | 24 | fun updateDataSet(settingsItems: List) { 25 | this.settingsItems = settingsItems 26 | notifyDataSetChanged() 27 | } 28 | 29 | override fun getItemViewType(position: Int) = settingsItems[position].type 30 | 31 | @SuppressLint("SetTextI18n") 32 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 33 | val item = settingsItems[position] 34 | if (holder is ItemViewHolder) { 35 | holder.itemView.apply { 36 | nameView.text = item.name 37 | valueView.text = item.value 38 | setOnClickListener { item.onClick?.invoke() } 39 | } 40 | } else if (holder is HeaderViewHolder) { 41 | (holder.itemView as TextView).apply { 42 | text = item.name 43 | } 44 | } 45 | } 46 | 47 | class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) 48 | class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) 49 | } -------------------------------------------------------------------------------- /example/src/main/java/co/infinum/example/SettingsItem.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.example 2 | 3 | data class SettingsItem( 4 | val name: String, 5 | val value: String? = null, 6 | val type: Int = 0, 7 | val onClick: (() -> Unit)? = null 8 | ) -------------------------------------------------------------------------------- /example/src/main/res/drawable-mdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Android-GoldenEye/5f9a7106043b68013aa65c65f8e552f64a46e8c5/example/src/main/res/drawable-mdpi/ic_logo.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-xhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Android-GoldenEye/5f9a7106043b68013aa65c65f8e552f64a46e8c5/example/src/main/res/drawable-xhdpi/ic_logo.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-xxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Android-GoldenEye/5f9a7106043b68013aa65c65f8e552f64a46e8c5/example/src/main/res/drawable-xxhdpi/ic_logo.png -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_arrow_up.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_record_video.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_switch_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_take_picture.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | 31 | 32 | 40 | 41 | 46 | 47 | 56 | 57 | 66 | 67 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 97 | 98 | 104 | 105 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /example/src/main/res/layout/header_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /example/src/main/res/layout/list.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #ff4040 6 | 7 | -------------------------------------------------------------------------------- /example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GoldenEye 3 | 4 | -------------------------------------------------------------------------------- /example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /goldeneye/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'org.jetbrains.dokka' 4 | 5 | android { 6 | compileSdkVersion sdk.target 7 | defaultConfig { 8 | minSdkVersion sdk.min 9 | targetSdkVersion sdk.target 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}" 15 | implementation "com.android.support:appcompat-v7:${versions.google}" 16 | } 17 | 18 | apply from: '../tasks.gradle' 19 | apply from: 'publish.gradle' 20 | 21 | preBuild.dependsOn ':goldeneye:generateReadme' 22 | -------------------------------------------------------------------------------- /goldeneye/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /goldeneye/publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "maven-publish" 2 | apply plugin: "signing" 3 | 4 | task sourcesJar(type: Jar) { 5 | archiveClassifier.set('sources') 6 | from android.sourceSets.main.kotlin.srcDirs 7 | } 8 | 9 | task javadocsJar(type: Jar, dependsOn: "dokkaJavadoc") { 10 | archiveClassifier.set("javadoc") 11 | from dokkaJavadoc.outputDirectory 12 | } 13 | 14 | afterEvaluate { 15 | publishing { 16 | repositories { 17 | maven { 18 | name sonatype.name() 19 | url sonatype.url() 20 | credentials { 21 | username sonatype.username() 22 | password sonatype.password() 23 | } 24 | } 25 | } 26 | publications { 27 | release(MavenPublication) { 28 | groupId = 'co.infinum' 29 | version = versions.goldeneye 30 | 31 | artifactId = 'goldeneye' 32 | 33 | artifact bundleReleaseAar 34 | artifact sourcesJar 35 | artifact javadocsJar 36 | 37 | pom { 38 | name = 'GoldenEye' 39 | description = 'A wrapper for Camera1 and Camera2 API which exposes simple to use interface.' 40 | url = 'https://github.com/infinum/Android-GoldenEye' 41 | licenses { 42 | license { 43 | name = 'The Apache License, Version 2.0' 44 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 45 | } 46 | } 47 | developers { 48 | developer { 49 | id = 'novakmarijan' 50 | name = 'Marijan Novak' 51 | email = 'marijan.novak@infinum.com' 52 | } 53 | } 54 | scm { 55 | connection = 'https://github.com/infinum/Android-GoldenEye.git' 56 | developerConnection = 'https://github.com/infinum/Android-GoldenEye.git' 57 | url = 'https://github.com/infinum/Android-GoldenEye' 58 | } 59 | } 60 | pom.withXml { 61 | def root = asNode() 62 | def dependenciesNode = root.appendNode('dependencies') 63 | configurations.implementation.allDependencies.each { 64 | def dependencyNode = dependenciesNode.appendNode('dependency') 65 | dependencyNode.appendNode('groupId', it.group) 66 | dependencyNode.appendNode('artifactId', it.name) 67 | dependencyNode.appendNode('version', it.version) 68 | } 69 | } 70 | signing { 71 | sign publishing.publications.release 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /goldeneye/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/BaseGoldenEye.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye 2 | 3 | import android.Manifest 4 | import android.graphics.Bitmap 5 | import android.support.annotation.RequiresPermission 6 | import android.view.TextureView 7 | import co.infinum.goldeneye.config.CameraConfig 8 | import co.infinum.goldeneye.config.CameraInfo 9 | import co.infinum.goldeneye.models.CameraApi 10 | import co.infinum.goldeneye.models.CameraState 11 | import java.io.File 12 | 13 | internal abstract class BaseGoldenEye( 14 | version: CameraApi 15 | ) : GoldenEye { 16 | companion object { 17 | lateinit var version: CameraApi 18 | var state = CameraState.CLOSED 19 | } 20 | 21 | init { 22 | BaseGoldenEye.version = version 23 | } 24 | 25 | protected val isConfigAvailable: Boolean 26 | get() = when (state) { 27 | CameraState.CLOSED, 28 | CameraState.INITIALIZING -> false 29 | CameraState.READY, 30 | CameraState.ACTIVE, 31 | CameraState.TAKING_PICTURE, 32 | CameraState.RECORDING_VIDEO -> true 33 | } 34 | 35 | @RequiresPermission(Manifest.permission.CAMERA) 36 | override fun open(textureView: TextureView, cameraInfo: CameraInfo, 37 | onReady: ((CameraConfig) -> Unit)?, onActive: (() -> Unit)?, onError: (Throwable) -> Unit 38 | ) { 39 | open(textureView, cameraInfo, object : InitCallback() { 40 | 41 | override fun onReady(config: CameraConfig) { 42 | onReady?.invoke(config) 43 | } 44 | 45 | override fun onActive() { 46 | onActive?.invoke() 47 | } 48 | 49 | override fun onError(t: Throwable) { 50 | onError(t) 51 | } 52 | }) 53 | } 54 | 55 | override fun takePicture(onPictureTaken: (Bitmap) -> Unit, onError: (Throwable) -> Unit, onShutter: (() -> Unit)?) { 56 | takePicture(object : PictureCallback() { 57 | override fun onPictureTaken(picture: Bitmap) { 58 | onPictureTaken(picture) 59 | } 60 | 61 | override fun onError(t: Throwable) { 62 | onError(t) 63 | } 64 | 65 | override fun onShutter() { 66 | onShutter?.invoke() 67 | } 68 | }) 69 | } 70 | 71 | override fun startRecording(file: File, onVideoRecorded: (File) -> Unit, onError: (Throwable) -> Unit) { 72 | startRecording(file, object : VideoCallback { 73 | override fun onVideoRecorded(file: File) { 74 | onVideoRecorded(file) 75 | } 76 | 77 | override fun onError(t: Throwable) { 78 | onError(t) 79 | } 80 | }) 81 | } 82 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/Callbacks.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Point 5 | import co.infinum.goldeneye.config.CameraConfig 6 | import java.io.File 7 | 8 | /** 9 | * [onPictureTaken] is received iff picture is successfully 10 | * taken and transformation is finished. 11 | * 12 | * [onError] is received if any error happens during the initialization 13 | * or while picture is being taken. 14 | * 15 | * [onShutter] method is called when picture is taken 16 | * and before it is processed. It can be useful to let 17 | * the user know that picture is taken with sound or 18 | * some other alert. 19 | * 20 | * @see GoldenEye.takePicture 21 | */ 22 | abstract class PictureCallback { 23 | abstract fun onPictureTaken(picture: Bitmap) 24 | abstract fun onError(t: Throwable) 25 | open fun onShutter() {} 26 | } 27 | 28 | /** 29 | * [onVideoRecorded] method returns the same file that is passed 30 | * in [GoldenEye.startRecording], it's just a convenience that 31 | * you don't need to keep reference to the file yourself. 32 | * 33 | * [onError] is received if any error happens during the initialization 34 | * or while video is being recorded. 35 | * 36 | * @see GoldenEye.startRecording 37 | */ 38 | interface VideoCallback { 39 | fun onVideoRecorded(file: File) 40 | fun onError(t: Throwable) 41 | } 42 | 43 | /** 44 | * [onReady] is received when Camera is opened and config is ready 45 | * to be modified. It is called just before preview is shown. 46 | * 47 | * [onActive] is received when Camera preview is active and visible. 48 | * 49 | * [onError] is received if any error happens during the initialization. 50 | * 51 | * @see GoldenEye.open 52 | */ 53 | abstract class InitCallback { 54 | open fun onReady(config: CameraConfig) {} 55 | open fun onActive() {} 56 | abstract fun onError(t: Throwable) 57 | } 58 | 59 | /** 60 | * Callback can be used to display some focus animation to the user 61 | * on the focused area. 62 | * 63 | * [onFocusChanged] is received each time camera focus is changed via 64 | * tap to focus functionality. 65 | * [Point] received represents (x,y) where user clicked on the [android.view.TextureView] 66 | * which is used for camera preview. 67 | */ 68 | interface OnFocusChangedCallback { 69 | fun onFocusChanged(point: Point) 70 | } 71 | 72 | /** 73 | * [onZoomChanged] is called on every zoom value change. 74 | * 75 | * @see CameraConfig.zoom 76 | */ 77 | interface OnZoomChangedCallback { 78 | fun onZoomChanged(zoom: Int) 79 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/Exceptions.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye 2 | 3 | object MissingCameraPermissionException : 4 | Exception("Cannot open camera without Camera permission.") 5 | 6 | object PictureConversionException : 7 | Exception("Failed to process picture.") 8 | 9 | object TaskOnMainThreadException : 10 | Exception("Heavy tasks must not be handled on MainThread!") 11 | 12 | object MediaRecorderDeadException: 13 | Exception("Media recorder died for unknown reason.") 14 | 15 | object CameraFailedToOpenException : 16 | Exception("For some unknown reason, camera failed to open.") 17 | 18 | object IllegalEnumException : 19 | Exception("Trying to convert illegal enum to Camera value. This is library error that should be reported.") 20 | 21 | object ThreadNotStartedException : 22 | Exception("Trying to fetch [backgroundHandler] but background Thread is not started.") 23 | 24 | object CameraConfigurationFailedException : 25 | Exception("For some unknown reason, camera configuration failed.") 26 | 27 | object ExternalVideoRecordingNotSupportedException : 28 | Exception("GoldenEye does not support video recording for external cameras.") 29 | 30 | object IllegalCharacteristicsException : 31 | Exception("Camera Characteristics are [NULL]") 32 | 33 | object CameraConfigNotAvailableException : 34 | Exception("Camera configuration is not available. Be sure to wait for InitCallback.onReady callback.") 35 | 36 | class CameraNotActiveException : 37 | Exception("Camera is currently not active. State = [${BaseGoldenEye.state}]") -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/Logger.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye 2 | 3 | /** 4 | * Logging interface that must be implemented in case you want 5 | * to see GoldenEye's logs. 6 | * 7 | * Logging is disabled by default. 8 | */ 9 | interface Logger { 10 | fun log(message: String) 11 | fun log(t: Throwable) 12 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/PictureTransformation.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye 2 | 3 | import android.graphics.Bitmap 4 | import co.infinum.goldeneye.config.CameraConfig 5 | import co.infinum.goldeneye.extensions.applyMatrix 6 | import co.infinum.goldeneye.extensions.mirror 7 | import co.infinum.goldeneye.extensions.rotate 8 | import co.infinum.goldeneye.models.Facing 9 | import co.infinum.goldeneye.utils.LogDelegate 10 | import co.infinum.goldeneye.utils.LogDelegate.log 11 | import kotlin.math.log 12 | 13 | /** 14 | * Implement picture transformation after picture is taken. 15 | * 16 | * [transform] is called after [PictureCallback.onShutter] and before [PictureCallback.onPictureTaken]. 17 | * Bitmap returned from [transform] is delegated to [PictureCallback.onPictureTaken]. 18 | * 19 | * Keep in mind that when working with Bitmaps, [OutOfMemoryError] is something 20 | * that can easily happen! 21 | */ 22 | interface PictureTransformation { 23 | 24 | /** 25 | * @param picture is original bitmap returned by the camera. It is rotated for [CameraConfig.orientation] 26 | * degrees and mirrored if front camera is used. 27 | * @param config current camera config that can be used to calculate and measure necessary transformations. 28 | * @param orientationDifference represents orientation difference between device and camera. 29 | * 30 | * NOTE: If [OutOfMemoryError] happens, original bitmap is returned! 31 | * 32 | * @return bitmap that will be delegated to [PictureCallback.onPictureTaken] 33 | */ 34 | fun transform(picture: Bitmap, config: CameraConfig, orientationDifference: Float): Bitmap 35 | 36 | /** 37 | * Default [PictureTransformation] implementation. It rotates the image to keep in sync with 38 | * device orientation and mirrors the image if front camera is used. 39 | * 40 | * In case of error, original bitmap is returned. 41 | */ 42 | object Default : PictureTransformation { 43 | override fun transform(picture: Bitmap, config: CameraConfig, orientationDifference: Float) = 44 | try { 45 | picture.applyMatrix { 46 | val cx = picture.width / 2f 47 | val cy = picture.height / 2f 48 | val degrees = if (config.facing == Facing.FRONT) -orientationDifference else orientationDifference 49 | rotate(degrees, cx, cy) 50 | if (config.facing == Facing.FRONT) { 51 | mirror() 52 | } 53 | } 54 | } catch (t: Throwable) { 55 | log("Failed to transform picture. Returning raw picture.", t) 56 | picture 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/AdvancedFeatureConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.models.AntibandingMode 4 | import co.infinum.goldeneye.models.CameraProperty 5 | import co.infinum.goldeneye.models.ColorEffectMode 6 | import co.infinum.goldeneye.models.WhiteBalanceMode 7 | import co.infinum.goldeneye.utils.LogDelegate.log 8 | 9 | interface AdvancedFeatureConfig { 10 | /** 11 | * Returns currently active White balance mode. 12 | * When new White balance mode is set, it is automatically updated on current camera. 13 | * 14 | * Default value is the first supported mode of [WhiteBalanceMode.AUTO], [WhiteBalanceMode.OFF]. 15 | * If none is supported [WhiteBalanceMode.UNKNOWN] is used. 16 | * 17 | * @see WhiteBalanceMode 18 | */ 19 | var whiteBalanceMode: WhiteBalanceMode 20 | 21 | /** 22 | * Returns list of available White balance modes. In case White balance is not supported, 23 | * empty list will be returned. 24 | * 25 | * @see WhiteBalanceMode 26 | */ 27 | val supportedWhiteBalanceModes: List 28 | 29 | /** 30 | * Returns currently active Color effect mode. 31 | * When new Color effect mode is set, it is automatically updated on current camera. 32 | * 33 | * Default value is [ColorEffectMode.NONE] if supported, otherwise [ColorEffectMode.UNKNOWN]. 34 | * 35 | * @see ColorEffectMode 36 | */ 37 | var colorEffectMode: ColorEffectMode 38 | 39 | /** 40 | * Returns list of available Color effect modes. In case Color effect is not supported, 41 | * empty list will be returned. 42 | * 43 | * @see ColorEffectMode 44 | */ 45 | val supportedColorEffectModes: List 46 | 47 | /** 48 | * Returns currently active Antibanding mode. 49 | * When new Antibanding mode is set, it is automatically updated on current camera. 50 | * 51 | * Default value is the first supported mode of [AntibandingMode.AUTO], [AntibandingMode.OFF]. 52 | * If none is supported [AntibandingMode.UNKNOWN] is used. 53 | * 54 | * @see AntibandingMode 55 | */ 56 | var antibandingMode: AntibandingMode 57 | 58 | /** 59 | * Returns list of available Antibanding modes. In case Antibanding is not supported, 60 | * empty list will be returned. 61 | * 62 | * @see AntibandingMode 63 | */ 64 | val supportedAntibandingModes: List 65 | } 66 | 67 | internal abstract class BaseAdvancedFeatureConfig( 68 | private val advancedFeaturesEnabled: Boolean, 69 | private val onUpdateCallback: (CameraProperty) -> Unit 70 | ) : AdvancedFeatureConfig { 71 | 72 | lateinit var characteristics: T 73 | 74 | override var whiteBalanceMode = WhiteBalanceMode.UNKNOWN 75 | get() = when { 76 | field != WhiteBalanceMode.UNKNOWN -> field 77 | supportedWhiteBalanceModes.contains(WhiteBalanceMode.AUTO) -> WhiteBalanceMode.AUTO 78 | else -> WhiteBalanceMode.UNKNOWN 79 | } 80 | set(value) { 81 | when { 82 | advancedFeaturesEnabled.not() -> logAdvancedFeaturesDisabled() 83 | supportedWhiteBalanceModes.contains(value) -> { 84 | field = value 85 | onUpdateCallback(CameraProperty.WHITE_BALANCE) 86 | } 87 | else -> log("Unsupported WhiteBalance [$value]") 88 | } 89 | } 90 | 91 | override var colorEffectMode = ColorEffectMode.UNKNOWN 92 | get() = when { 93 | field != ColorEffectMode.UNKNOWN -> field 94 | else -> ColorEffectMode.UNKNOWN 95 | } 96 | set(value) { 97 | when { 98 | advancedFeaturesEnabled.not() -> logAdvancedFeaturesDisabled() 99 | supportedColorEffectModes.contains(value) -> { 100 | field = value 101 | onUpdateCallback(CameraProperty.COLOR_EFFECT) 102 | } 103 | else -> log("Unsupported ColorEffect [$value]") 104 | } 105 | } 106 | 107 | override var antibandingMode = AntibandingMode.UNKNOWN 108 | get() = when { 109 | field != AntibandingMode.UNKNOWN -> field 110 | supportedAntibandingModes.contains(AntibandingMode.AUTO) -> AntibandingMode.AUTO 111 | else -> AntibandingMode.UNKNOWN 112 | } 113 | set(value) { 114 | when { 115 | advancedFeaturesEnabled.not() -> logAdvancedFeaturesDisabled() 116 | supportedAntibandingModes.contains(value) -> { 117 | field = value 118 | onUpdateCallback(CameraProperty.ANTIBANDING) 119 | } 120 | else -> log("Unsupported Antibanding [$value]") 121 | } 122 | } 123 | 124 | private fun logAdvancedFeaturesDisabled() { 125 | log("Advanced features disabled. Use GoldenEye#Builder.withAdvancedFeatures() method to activate them.") 126 | } 127 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/BasicFeatureConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.models.* 4 | import co.infinum.goldeneye.utils.LogDelegate 5 | 6 | interface BasicFeatureConfig { 7 | 8 | /** 9 | * Returns current JPEG quality. The value must be Integer between 1 and 100 (inclusive). 10 | * It is dynamically applied when capture is triggered. 11 | * 12 | * Default value is 100. 13 | */ 14 | var pictureQuality: Int 15 | 16 | /** 17 | * Tap to focus toggle. [co.infinum.goldeneye.OnFocusChangedCallback] is triggered 18 | * every time focus change happens. 19 | * 20 | * If tap to focus is supported, default value is true, otherwise false. 21 | * 22 | * @see co.infinum.goldeneye.OnFocusChangedCallback 23 | */ 24 | var tapToFocusEnabled: Boolean 25 | 26 | /** 27 | * Return whether Tap to focus is even supported for current camera. 28 | * 29 | * Some cameras do not support [FocusMode.AUTO], so tap to focus 30 | * is not supported for those devices. 31 | */ 32 | val isTapToFocusSupported: Boolean 33 | 34 | /** 35 | * While [FocusMode.CONTINUOUS_PICTURE] or [FocusMode.CONTINUOUS_VIDEO] is active, tap to focus 36 | * must hijack its focus. After [tapToFocusResetDelay] milliseconds, Focus handling 37 | * will be given back to Camera. 38 | * 39 | * Delay is measured in milliseconds! 40 | * 41 | * Default value is 7_500. 42 | */ 43 | var tapToFocusResetDelay: Long 44 | 45 | /** 46 | * Returns currently active Flash mode. 47 | * When new Flash mode is set, it is automatically updated on current camera. 48 | * 49 | * Default value is the first supported mode of [FlashMode.AUTO], [FlashMode.OFF]. 50 | * If none is supported [FlashMode.UNKNOWN] is used. 51 | * 52 | * @see FlashMode 53 | */ 54 | var flashMode: FlashMode 55 | 56 | /** 57 | * Returns list of available Flash modes. In case Flash is not supported, 58 | * empty list will be returned. 59 | * 60 | * @see FlashMode 61 | */ 62 | val supportedFlashModes: List 63 | 64 | /** 65 | * Returns currently active Focus mode. 66 | * When new Focus mode is set, it is automatically updated on current camera. 67 | * 68 | * Default value is the first supported mode of [FocusMode.CONTINUOUS_PICTURE], [FocusMode.AUTO]. 69 | * If none is supported [FocusMode.UNKNOWN] is used. 70 | * 71 | * @see FocusMode 72 | */ 73 | var focusMode: FocusMode 74 | 75 | /** 76 | * Returns list of available Focus modes. In case Focus is not supported, 77 | * empty list will be returned. 78 | * 79 | * @see FocusMode 80 | */ 81 | val supportedFocusModes: List 82 | } 83 | 84 | internal abstract class BaseBasicFeatureConfig( 85 | private val onUpdateCallback: (CameraProperty) -> Unit 86 | ) : BasicFeatureConfig { 87 | 88 | lateinit var characteristics: T 89 | 90 | override var tapToFocusEnabled = true 91 | get() = field && isTapToFocusSupported 92 | set(value) { 93 | if (isTapToFocusSupported) { 94 | field = value 95 | } else { 96 | LogDelegate.log("Unsupported Tap to focus.") 97 | } 98 | } 99 | 100 | override var tapToFocusResetDelay = 7_500L 101 | set(value) { 102 | if (value > 0) { 103 | field = value 104 | } else { 105 | LogDelegate.log("Reset focus delay must be bigger than 0.") 106 | } 107 | } 108 | 109 | override var pictureQuality = 100 110 | set(value) { 111 | field = value.coerceIn(1, 100) 112 | } 113 | 114 | override var flashMode = FlashMode.UNKNOWN 115 | get() = when { 116 | field != FlashMode.UNKNOWN -> field 117 | supportedFlashModes.contains(FlashMode.AUTO) -> FlashMode.AUTO 118 | supportedFlashModes.contains(FlashMode.OFF) -> FlashMode.OFF 119 | else -> FlashMode.UNKNOWN 120 | } 121 | set(value) { 122 | if (supportedFlashModes.contains(value)) { 123 | field = value 124 | onUpdateCallback(CameraProperty.FLASH) 125 | } else { 126 | LogDelegate.log("Unsupported FlashMode [$value]") 127 | } 128 | } 129 | 130 | override var focusMode = FocusMode.UNKNOWN 131 | get() = when { 132 | field != FocusMode.UNKNOWN -> field 133 | supportedFocusModes.contains(FocusMode.CONTINUOUS_PICTURE) -> FocusMode.CONTINUOUS_PICTURE 134 | supportedFocusModes.contains(FocusMode.AUTO) -> FocusMode.AUTO 135 | else -> FocusMode.UNKNOWN 136 | } 137 | set(value) { 138 | if (supportedFocusModes.contains(value)) { 139 | field = value 140 | onUpdateCallback(CameraProperty.FOCUS) 141 | } else { 142 | LogDelegate.log("Unsupported FocusMode [$value]") 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/CameraConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.IllegalCharacteristicsException 4 | 5 | /** 6 | * Main Camera configuration interface. It wraps all smaller interfaces 7 | * just to split implementation logic. For detailed documentation look 8 | * into extended interfaces. 9 | */ 10 | interface CameraConfig : 11 | CameraInfo, 12 | VideoConfig, 13 | BasicFeatureConfig, 14 | AdvancedFeatureConfig, 15 | SizeConfig, 16 | ZoomConfig 17 | 18 | internal abstract class CameraConfigImpl( 19 | var cameraInfo: CameraInfo, 20 | var videoConfig: BaseVideoConfig, 21 | var basicFeatureConfig: BaseBasicFeatureConfig, 22 | var advancedFeatureConfig: BaseAdvancedFeatureConfig, 23 | var sizeConfig: BaseSizeConfig, 24 | var zoomConfig: BaseZoomConfig 25 | ) : CameraConfig, 26 | CameraInfo by cameraInfo, 27 | VideoConfig by videoConfig, 28 | BasicFeatureConfig by basicFeatureConfig, 29 | AdvancedFeatureConfig by advancedFeatureConfig, 30 | SizeConfig by sizeConfig, 31 | ZoomConfig by zoomConfig { 32 | 33 | var characteristics: T? = null 34 | set(value) { 35 | field = value 36 | if (value != null) { 37 | sizeConfig.characteristics = value 38 | videoConfig.characteristics = value 39 | basicFeatureConfig.characteristics = value 40 | advancedFeatureConfig.characteristics = value 41 | zoomConfig.characteristics = value 42 | } else { 43 | throw IllegalCharacteristicsException 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/CameraInfo.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.models.Facing 4 | 5 | /** 6 | * General Camera info used by Camera APIs to open Camera. 7 | */ 8 | interface CameraInfo { 9 | /** 10 | * Camera ID. 11 | */ 12 | val id: String 13 | 14 | /** 15 | * Camera orientation. Camera has its own orientation that is not in sync 16 | * with Device orientation. 17 | */ 18 | val orientation: Int 19 | 20 | /** 21 | * Camera facing can be either FRONT, BACK or EXTERNAL. 22 | * EXTERNAL cameras are mostly handled same as BACK cameras. 23 | */ 24 | val facing: Facing 25 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/SizeConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import android.media.CamcorderProfile 4 | import co.infinum.goldeneye.BaseGoldenEye 5 | import co.infinum.goldeneye.models.CameraProperty 6 | import co.infinum.goldeneye.models.CameraState 7 | import co.infinum.goldeneye.models.PreviewScale 8 | import co.infinum.goldeneye.models.Size 9 | import co.infinum.goldeneye.utils.CameraUtils 10 | import co.infinum.goldeneye.utils.LogDelegate 11 | 12 | interface SizeConfig { 13 | 14 | /** 15 | * Returns currently active preview size. Preview size depends on current [previewScale]. 16 | * Preview size is not the size of the TextureView that is used for preview! Preview has 17 | * its own size that is then scaled inside of the TextureView via Matrix. 18 | * 19 | * @see PreviewScale 20 | */ 21 | var previewSize: Size 22 | 23 | /** 24 | * List of supported preview sizes. Empty list is returned in case of error. 25 | */ 26 | val supportedPreviewSizes: List 27 | 28 | /** 29 | * Defines the size of taken picture. 30 | * 31 | * Returns currently active picture size. 32 | */ 33 | var pictureSize: Size 34 | 35 | /** 36 | * List of supported picture sizes. Empty list is returned in case of error. 37 | */ 38 | val supportedPictureSizes: List 39 | 40 | /** 41 | * Returns current video size defined by [VideoConfig.videoQuality]. 42 | */ 43 | val videoSize: Size 44 | 45 | /** 46 | * @see PreviewScale 47 | */ 48 | var previewScale: PreviewScale 49 | } 50 | 51 | internal abstract class BaseSizeConfig( 52 | private val cameraInfo: CameraInfo, 53 | private val videoConfig: VideoConfig, 54 | private val onUpdateCallback: (CameraProperty) -> Unit 55 | ) : SizeConfig { 56 | 57 | lateinit var characteristics: T 58 | 59 | override var previewSize = Size.UNKNOWN 60 | get() = when (previewScale) { 61 | PreviewScale.MANUAL, 62 | PreviewScale.MANUAL_FIT, 63 | PreviewScale.MANUAL_FILL -> field 64 | PreviewScale.AUTO_FIT, 65 | PreviewScale.AUTO_FILL -> 66 | if (BaseGoldenEye.state == CameraState.RECORDING_VIDEO) { 67 | CameraUtils.findBestMatchingSize(videoSize, supportedPreviewSizes) 68 | } else { 69 | CameraUtils.findBestMatchingSize(pictureSize, supportedPreviewSizes) 70 | } 71 | } 72 | set(value) { 73 | if (supportedPreviewSizes.contains(value)) { 74 | field = value 75 | onUpdateCallback(CameraProperty.PREVIEW_SIZE) 76 | } else { 77 | LogDelegate.log("Unsupported PreviewSize [$value]") 78 | } 79 | } 80 | 81 | override var pictureSize = Size.UNKNOWN 82 | get() = when { 83 | field != Size.UNKNOWN -> field 84 | supportedPictureSizes.isNotEmpty() -> supportedPictureSizes[0] 85 | else -> Size.UNKNOWN 86 | } 87 | set(value) { 88 | if (supportedPictureSizes.contains(value)) { 89 | field = value 90 | onUpdateCallback(CameraProperty.PICTURE_SIZE) 91 | } else { 92 | LogDelegate.log("Unsupported PictureSize [$value]") 93 | } 94 | } 95 | 96 | override val videoSize: Size 97 | get() { 98 | return if (cameraInfo.id.toIntOrNull() != null) { 99 | val profile = CamcorderProfile.get(cameraInfo.id.toInt(), videoConfig.videoQuality.key) 100 | Size(profile.videoFrameWidth, profile.videoFrameHeight) 101 | } else { 102 | Size.UNKNOWN 103 | } 104 | } 105 | 106 | override var previewScale: PreviewScale = PreviewScale.AUTO_FIT 107 | set(value) { 108 | field = value 109 | onUpdateCallback(CameraProperty.PREVIEW_SCALE) 110 | } 111 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/VideoConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.models.CameraProperty 4 | import co.infinum.goldeneye.models.VideoQuality 5 | import co.infinum.goldeneye.utils.LogDelegate 6 | 7 | interface VideoConfig { 8 | /** 9 | * Defines the quality of recorded video. 10 | * 11 | * Default value is the first supported of [VideoQuality.RESOLUTION_2160P], 12 | * [VideoQuality.RESOLUTION_1080P], [VideoQuality.RESOLUTION_720P], [VideoQuality.HIGH], 13 | * [VideoQuality.LOW]. If none is supported, [VideoQuality.UNKNOWN] is used. 14 | * 15 | * @see VideoQuality 16 | */ 17 | var videoQuality: VideoQuality 18 | 19 | /** 20 | * Returns list of supported video qualities. 21 | * 22 | * Empty list is returned in case of error or for external cameras. 23 | * 24 | * IMPORTANT: For now, video recording via external cameras is disabled because 25 | * it is impossible to fetch available video qualities via current API. 26 | */ 27 | val supportedVideoQualities: List 28 | 29 | /** 30 | * Video stabilization toggle. 31 | * 32 | * Default value is false. 33 | */ 34 | var videoStabilizationEnabled: Boolean 35 | 36 | /** 37 | * Returns whether video stabilization is supported. 38 | */ 39 | val isVideoStabilizationSupported: Boolean 40 | } 41 | 42 | internal abstract class BaseVideoConfig( 43 | private val onUpdateCallback: (CameraProperty) -> Unit 44 | ) : VideoConfig { 45 | 46 | lateinit var characteristics: T 47 | 48 | override var videoQuality = VideoQuality.UNKNOWN 49 | get() = when { 50 | field != VideoQuality.UNKNOWN -> field 51 | supportedVideoQualities.contains(VideoQuality.RESOLUTION_2160P) -> VideoQuality.RESOLUTION_2160P 52 | supportedVideoQualities.contains(VideoQuality.RESOLUTION_1080P) -> VideoQuality.RESOLUTION_1080P 53 | supportedVideoQualities.contains(VideoQuality.RESOLUTION_720P) -> VideoQuality.RESOLUTION_720P 54 | supportedVideoQualities.contains(VideoQuality.HIGH) -> VideoQuality.HIGH 55 | supportedVideoQualities.contains(VideoQuality.LOW) -> VideoQuality.LOW 56 | else -> VideoQuality.UNKNOWN 57 | } 58 | set(value) { 59 | if (supportedVideoQualities.contains(value)) { 60 | field = value 61 | } else { 62 | LogDelegate.log("Unsupported VideoQuality [$value]") 63 | } 64 | } 65 | 66 | override var videoStabilizationEnabled = false 67 | get() = isVideoStabilizationSupported && field 68 | set(value) { 69 | if (isVideoStabilizationSupported) { 70 | field = value 71 | onUpdateCallback(CameraProperty.VIDEO_STABILIZATION) 72 | } else { 73 | LogDelegate.log("VideoStabilization not supported.") 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/ZoomConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config 2 | 3 | import co.infinum.goldeneye.OnZoomChangedCallback 4 | import co.infinum.goldeneye.models.CameraProperty 5 | import co.infinum.goldeneye.utils.LogDelegate 6 | 7 | interface ZoomConfig { 8 | 9 | /** 10 | * Defines current zoom ratio. [zoom] value is equal to zoom ratio * 100. 11 | * Value must be integer between 100 and [maxZoom]. 12 | * 13 | * Example - if zoom ratio is 2.5x, then [zoom] value is 250. 14 | * 15 | * Default value is 100. 16 | */ 17 | var zoom: Int 18 | 19 | /** 20 | * Defines max supported digital zoom. 21 | */ 22 | val maxZoom: Int 23 | 24 | /** 25 | * Returns whether zoom is supported. Some cameras do not support zoom. 26 | */ 27 | val isZoomSupported: Boolean 28 | 29 | /** 30 | * Pinch to zoom toggle. [co.infinum.goldeneye.OnZoomChangedCallback] is triggered 31 | * every time zoom change happens. 32 | * 33 | * Default value is true if zoom is supported, otherwise false. 34 | * 35 | * @see co.infinum.goldeneye.OnZoomChangedCallback 36 | */ 37 | var pinchToZoomEnabled: Boolean 38 | 39 | /** 40 | * Defines how sensitive pinch to zoom is. 41 | * 42 | * Value must be positive. Lower friction means bigger sensitivity. 43 | * 44 | * Default friction is 1f. 45 | */ 46 | var pinchToZoomFriction: Float 47 | } 48 | 49 | internal abstract class BaseZoomConfig( 50 | protected val onUpdateCallback: (CameraProperty) -> Unit, 51 | protected val onZoomChangedCallback: OnZoomChangedCallback? 52 | ) : ZoomConfig { 53 | 54 | lateinit var characteristics: T 55 | 56 | override var pinchToZoomEnabled = true 57 | get() = field && isZoomSupported 58 | set(value) { 59 | field = isZoomSupported && value 60 | } 61 | 62 | override var pinchToZoomFriction = 1f 63 | set(value) { 64 | if (value > 0) { 65 | field = value 66 | } else { 67 | LogDelegate.log("Pinch to zoom friction must be bigger than 0.") 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/AdvancedFeatureConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.config.BaseAdvancedFeatureConfig 7 | import co.infinum.goldeneye.models.AntibandingMode 8 | import co.infinum.goldeneye.models.CameraProperty 9 | import co.infinum.goldeneye.models.ColorEffectMode 10 | import co.infinum.goldeneye.models.WhiteBalanceMode 11 | 12 | internal class AdvancedFeatureConfigImpl( 13 | advancedFeaturesEnabled: Boolean, 14 | onUpdateCallback: (CameraProperty) -> Unit 15 | ) : BaseAdvancedFeatureConfig(advancedFeaturesEnabled, onUpdateCallback) { 16 | 17 | override val supportedWhiteBalanceModes: List by lazy { 18 | characteristics.supportedWhiteBalance 19 | ?.map { WhiteBalanceMode.fromCamera1(it) } 20 | ?.filter { it != WhiteBalanceMode.UNKNOWN } 21 | ?: emptyList() 22 | 23 | } 24 | 25 | override val supportedColorEffectModes: List by lazy { 26 | characteristics.supportedColorEffects 27 | ?.map { ColorEffectMode.fromCamera1(it) } 28 | ?.filter { it != ColorEffectMode.UNKNOWN } 29 | ?: emptyList() 30 | 31 | } 32 | 33 | override val supportedAntibandingModes: List by lazy { 34 | characteristics.supportedAntibanding 35 | ?.map { AntibandingMode.fromCamera1(it) } 36 | ?.filter { it != AntibandingMode.UNKNOWN } 37 | ?: emptyList() 38 | } 39 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/BasicFeatureConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "ConvertCallChainIntoSequence") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.config.BaseBasicFeatureConfig 7 | import co.infinum.goldeneye.models.CameraProperty 8 | import co.infinum.goldeneye.models.FlashMode 9 | import co.infinum.goldeneye.models.FocusMode 10 | 11 | internal class BasicFeatureConfigImpl( 12 | onUpdateCallback: (CameraProperty) -> Unit 13 | ) : BaseBasicFeatureConfig(onUpdateCallback) { 14 | 15 | override val isTapToFocusSupported: Boolean by lazy { 16 | characteristics.maxNumFocusAreas > 0 17 | } 18 | 19 | override val supportedFlashModes: List by lazy { 20 | characteristics.supportedFlashModes 21 | ?.map { FlashMode.fromCamera1(it) } 22 | ?.filter { it != FlashMode.UNKNOWN } 23 | ?: emptyList() 24 | } 25 | 26 | override val supportedFocusModes: List by lazy { 27 | characteristics.supportedFocusModes 28 | ?.map { FocusMode.fromCamera1(it) } 29 | ?.filter { it != FocusMode.UNKNOWN } 30 | ?: emptyList() 31 | } 32 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/Camera1ConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.config.* 7 | 8 | internal class Camera1ConfigImpl( 9 | cameraInfo: CameraInfo, 10 | videoConfig: BaseVideoConfig, 11 | basicFeatureConfig: BaseBasicFeatureConfig, 12 | sizeConfig: BaseSizeConfig, 13 | zoomConfig: BaseZoomConfig, 14 | advancedFeatureConfig: BaseAdvancedFeatureConfig 15 | ) : CameraConfigImpl(cameraInfo, videoConfig, basicFeatureConfig, advancedFeatureConfig, sizeConfig, zoomConfig) -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/ConfigUpdateHandler.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import android.os.Build 7 | import co.infinum.goldeneye.BaseGoldenEye 8 | import co.infinum.goldeneye.config.CameraConfig 9 | import co.infinum.goldeneye.extensions.updateParams 10 | import co.infinum.goldeneye.models.CameraProperty 11 | import co.infinum.goldeneye.models.CameraState 12 | import co.infinum.goldeneye.models.PreviewScale 13 | import co.infinum.goldeneye.models.Size 14 | 15 | /** 16 | * Handles property updates. Syncs CameraConfig with active Camera. 17 | * 18 | * Sometimes we have to restart the preview so we have to pass function 19 | * that does that because it is not accessible from this class otherwise. 20 | */ 21 | internal class ConfigUpdateHandler( 22 | private val camera: Camera, 23 | private val config: CameraConfig, 24 | private val restartPreview: () -> Unit 25 | ) { 26 | 27 | fun onPropertyUpdated(property: CameraProperty) { 28 | when (property) { 29 | CameraProperty.FOCUS -> camera.updateParams { focusMode = config.focusMode.toCamera1() } 30 | CameraProperty.FLASH -> camera.updateParams { flashMode = config.flashMode.toCamera1() } 31 | CameraProperty.COLOR_EFFECT -> camera.updateParams { colorEffect = config.colorEffectMode.toCamera1() } 32 | CameraProperty.ANTIBANDING -> camera.updateParams { antibanding = config.antibandingMode.toCamera1() } 33 | CameraProperty.WHITE_BALANCE -> camera.updateParams { whiteBalance = config.whiteBalanceMode.toCamera1() } 34 | CameraProperty.PICTURE_SIZE -> updatePictureSize(config.pictureSize, config.previewSize) 35 | CameraProperty.PREVIEW_SIZE -> updatePreviewSize(config.previewSize) 36 | CameraProperty.ZOOM -> camera.updateParams { zoom = zoomRatios.indexOf(config.zoom) } 37 | CameraProperty.VIDEO_STABILIZATION -> camera.updateParams { 38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 39 | videoStabilization = config.videoStabilizationEnabled 40 | } 41 | } 42 | CameraProperty.PREVIEW_SCALE -> restartPreview() 43 | } 44 | } 45 | 46 | private fun updatePictureSize(pictureSize: Size, previewSize: Size) { 47 | camera.updateParams { setPictureSize(pictureSize.width, pictureSize.height) } 48 | /* Update preview if AUTO_* mode is active and video is not being recorded */ 49 | if ((config.previewScale == PreviewScale.AUTO_FILL || config.previewScale == PreviewScale.AUTO_FIT) 50 | && BaseGoldenEye.state != CameraState.RECORDING_VIDEO 51 | ) { 52 | updatePreviewSize(previewSize) 53 | } 54 | } 55 | 56 | private fun updatePreviewSize(previewSize: Size) { 57 | camera.updateParams { setPreviewSize(previewSize.width, previewSize.height) } 58 | restartPreview() 59 | } 60 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/SizeConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "ConvertCallChainIntoSequence") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.config.BaseSizeConfig 7 | import co.infinum.goldeneye.config.CameraInfo 8 | import co.infinum.goldeneye.config.VideoConfig 9 | import co.infinum.goldeneye.models.CameraProperty 10 | import co.infinum.goldeneye.models.Size 11 | import co.infinum.goldeneye.models.toInternalSize 12 | 13 | internal class SizeConfigImpl( 14 | cameraInfo: CameraInfo, 15 | videoConfig: VideoConfig, 16 | onUpdateCallback: (CameraProperty) -> Unit 17 | ) : BaseSizeConfig(cameraInfo, videoConfig, onUpdateCallback) { 18 | 19 | override val supportedPreviewSizes: List by lazy { 20 | characteristics.supportedPreviewSizes?.map { it.toInternalSize() }?.sorted() ?: emptyList() 21 | } 22 | 23 | override val supportedPictureSizes: List by lazy { 24 | characteristics.supportedPictureSizes?.map { it.toInternalSize() }?.sorted() ?: emptyList() 25 | } 26 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/VideoConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import android.media.CamcorderProfile 7 | import android.os.Build 8 | import co.infinum.goldeneye.config.BaseVideoConfig 9 | import co.infinum.goldeneye.models.CameraProperty 10 | import co.infinum.goldeneye.models.VideoQuality 11 | 12 | internal class VideoConfigImpl( 13 | private val id: String, 14 | onUpdateCallback: (CameraProperty) -> Unit 15 | ) : BaseVideoConfig(onUpdateCallback) { 16 | 17 | override val isVideoStabilizationSupported: Boolean by lazy { 18 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && characteristics.isVideoStabilizationSupported 19 | } 20 | 21 | override val supportedVideoQualities: List by lazy { 22 | val id = id.toIntOrNull() ?: return@lazy emptyList() 23 | VideoQuality.values() 24 | /* This check is must have! Otherwise Camera1 would have false positive VideoQualities that crash the camera. */ 25 | .filter { it.isCamera2Required().not() } 26 | .filter { CamcorderProfile.hasProfile(id, it.key) && it != VideoQuality.UNKNOWN } 27 | } 28 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera1/ZoomConfigImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.config.camera1 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.OnZoomChangedCallback 7 | import co.infinum.goldeneye.config.BaseZoomConfig 8 | import co.infinum.goldeneye.models.CameraProperty 9 | import co.infinum.goldeneye.utils.LogDelegate 10 | import kotlin.math.abs 11 | 12 | internal class ZoomConfigImpl( 13 | onUpdateCallback: (CameraProperty) -> Unit, 14 | onZoomChangedCallback: OnZoomChangedCallback? 15 | ) : BaseZoomConfig(onUpdateCallback, onZoomChangedCallback) { 16 | 17 | override var zoom = 100 18 | set(value) { 19 | if (isZoomSupported) { 20 | /* Camera1 has random zoom ratios. Find zoom ratio that is closest to given value */ 21 | field = characteristics.zoomRatios?.minBy { abs(it - value) } ?: 100 22 | onUpdateCallback(CameraProperty.ZOOM) 23 | onZoomChangedCallback?.onZoomChanged(value) 24 | } else { 25 | LogDelegate.log("Unsupported ZoomLevel [$value]") 26 | } 27 | } 28 | 29 | override val maxZoom by lazy { 30 | characteristics.zoomRatios?.getOrNull(characteristics.maxZoom) ?: 100 31 | } 32 | 33 | override val isZoomSupported by lazy { 34 | characteristics.isZoomSupported 35 | } 36 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/AdvancedFeatureConfigImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | import co.infinum.goldeneye.config.BaseAdvancedFeatureConfig 7 | import co.infinum.goldeneye.models.AntibandingMode 8 | import co.infinum.goldeneye.models.CameraProperty 9 | import co.infinum.goldeneye.models.ColorEffectMode 10 | import co.infinum.goldeneye.models.WhiteBalanceMode 11 | 12 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 13 | internal class AdvancedFeatureConfigImpl( 14 | advancedFeaturesEnabled: Boolean, 15 | onUpdateCallback: (CameraProperty) -> Unit 16 | ) : BaseAdvancedFeatureConfig(advancedFeaturesEnabled, onUpdateCallback) { 17 | 18 | override val supportedWhiteBalanceModes: List by lazy { 19 | characteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES) 20 | ?.map { WhiteBalanceMode.fromCamera2(it) } 21 | ?.filter { it != WhiteBalanceMode.UNKNOWN } 22 | ?: emptyList() 23 | } 24 | 25 | override val supportedColorEffectModes: List by lazy { 26 | characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS) 27 | ?.map { ColorEffectMode.fromCamera2(it) } 28 | ?.filter { it != ColorEffectMode.UNKNOWN } 29 | ?: emptyList() 30 | } 31 | 32 | override val supportedAntibandingModes: List by lazy { 33 | characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES) 34 | ?.map { AntibandingMode.fromCamera2(it) } 35 | ?.filter { it != AntibandingMode.UNKNOWN } 36 | ?: emptyList() 37 | } 38 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/BasicFeatureConfig.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | import co.infinum.goldeneye.config.BaseBasicFeatureConfig 7 | import co.infinum.goldeneye.models.CameraProperty 8 | import co.infinum.goldeneye.models.FlashMode 9 | import co.infinum.goldeneye.models.FocusMode 10 | 11 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 12 | internal class BasicFeatureConfig( 13 | onUpdateCallback: (CameraProperty) -> Unit 14 | ) : BaseBasicFeatureConfig(onUpdateCallback) { 15 | 16 | override val isTapToFocusSupported: Boolean by lazy { 17 | characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF) ?: 0 > 0 18 | } 19 | 20 | override val supportedFlashModes: List by lazy { 21 | if (characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == false) { 22 | return@lazy emptyList() 23 | } 24 | 25 | val flashModes = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES) 26 | ?.map { FlashMode.fromCamera2(it) } 27 | ?.filter { it != FlashMode.UNKNOWN } 28 | ?.toMutableList() 29 | flashModes?.add(FlashMode.TORCH) 30 | flashModes ?: emptyList() 31 | } 32 | 33 | override val supportedFocusModes: List by lazy { 34 | characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES) 35 | ?.map { FocusMode.fromCamera2(it) } 36 | ?.filter { it != FocusMode.UNKNOWN } 37 | ?: emptyList() 38 | } 39 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/Camera2ConfigImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | import android.hardware.camera2.CameraCharacteristics.* 5 | import android.os.Build 6 | import android.support.annotation.RequiresApi 7 | import co.infinum.goldeneye.config.* 8 | 9 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 10 | internal class Camera2ConfigImpl( 11 | cameraInfo: CameraInfo, 12 | videoConfig: BaseVideoConfig, 13 | basicFeatureConfig: BaseBasicFeatureConfig, 14 | advancedFeatureConfig: BaseAdvancedFeatureConfig, 15 | sizeConfig: BaseSizeConfig, 16 | zoomConfig: BaseZoomConfig 17 | ) : CameraConfigImpl(cameraInfo, videoConfig, basicFeatureConfig, advancedFeatureConfig, sizeConfig, zoomConfig) { 18 | 19 | fun isHardwareAtLeastLimited() = 20 | characteristics?.get(INFO_SUPPORTED_HARDWARE_LEVEL) == INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED 21 | || isHardwareAtLeastFull() 22 | || isHardwareAtLeastLevel3() 23 | 24 | private fun isHardwareAtLeastFull() = 25 | characteristics?.get(INFO_SUPPORTED_HARDWARE_LEVEL) == INFO_SUPPORTED_HARDWARE_LEVEL_FULL 26 | || isHardwareAtLeastLevel3() 27 | 28 | private fun isHardwareAtLeastLevel3() = 29 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.N 30 | && characteristics?.get(INFO_SUPPORTED_HARDWARE_LEVEL) == INFO_SUPPORTED_HARDWARE_LEVEL_3 31 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/ConfigUpdateHandler.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CaptureRequest 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | import co.infinum.goldeneye.models.CameraProperty 7 | import co.infinum.goldeneye.models.FlashMode 8 | import co.infinum.goldeneye.sessions.SessionsManager 9 | import co.infinum.goldeneye.utils.CameraUtils 10 | 11 | /** 12 | * Handles property updates. Syncs CameraConfig with active Camera2 session. 13 | */ 14 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 15 | internal class ConfigUpdateHandler( 16 | private val sessionsManager: SessionsManager, 17 | private val config: Camera2ConfigImpl 18 | ) { 19 | 20 | fun onPropertyUpdated(property: CameraProperty) { 21 | when (property) { 22 | CameraProperty.FOCUS -> sessionsManager.updateSession { 23 | set(CaptureRequest.CONTROL_AF_MODE, config.focusMode.toCamera2()) 24 | } 25 | CameraProperty.FLASH -> { 26 | sessionsManager.resetFlashMode() 27 | sessionsManager.updateSession { updateFlashMode(this, config.flashMode) } 28 | } 29 | CameraProperty.COLOR_EFFECT -> sessionsManager.updateSession { 30 | set(CaptureRequest.CONTROL_EFFECT_MODE, config.colorEffectMode.toCamera2()) 31 | } 32 | CameraProperty.ANTIBANDING -> sessionsManager.updateSession { 33 | set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, config.antibandingMode.toCamera2()) 34 | } 35 | CameraProperty.WHITE_BALANCE -> sessionsManager.updateSession { 36 | set(CaptureRequest.CONTROL_AWB_MODE, config.whiteBalanceMode.toCamera2()) 37 | } 38 | CameraProperty.PICTURE_SIZE -> sessionsManager.restartSession() 39 | CameraProperty.PREVIEW_SIZE -> sessionsManager.restartSession() 40 | CameraProperty.ZOOM -> sessionsManager.updateSession { updateZoom(this) } 41 | CameraProperty.VIDEO_STABILIZATION -> updateVideoStabilization() 42 | CameraProperty.PREVIEW_SCALE -> sessionsManager.restartSession() 43 | } 44 | } 45 | 46 | private fun updateVideoStabilization() { 47 | sessionsManager.updateSession { 48 | val videoStabilizationMode = if (config.videoStabilizationEnabled) { 49 | CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON 50 | } else { 51 | CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF 52 | } 53 | set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, videoStabilizationMode) 54 | } 55 | } 56 | 57 | /** 58 | * Update Camera2 zoom by measuring ZoomRect. 59 | */ 60 | private fun updateZoom(requestBuilder: CaptureRequest.Builder) { 61 | /* Get active Rect size. This corresponds to actual camera size seen by Camera2 API */ 62 | val zoomedRect = CameraUtils.calculateCamera2ZoomRect(config) ?: return 63 | 64 | /* BAM */ 65 | requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomedRect) 66 | } 67 | 68 | /** 69 | * Update Flash mode. Edge case is if it is FlashMode.TORCH is applied. 70 | * Flash mode is handled by AE_MODE except FlashMode.TORCH. If TORCH is 71 | * set, make sure to disable AE_MODE, otherwise they clash. 72 | */ 73 | private fun updateFlashMode(requestBuilder: CaptureRequest.Builder, flashMode: FlashMode) { 74 | if (flashMode == FlashMode.TORCH) { 75 | requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) 76 | requestBuilder.set(CaptureRequest.FLASH_MODE, FlashMode.TORCH.toCamera2()) 77 | } else { 78 | requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF) 79 | requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashMode.toCamera2()) 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/SizeConfigImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.graphics.ImageFormat 4 | import android.graphics.SurfaceTexture 5 | import android.hardware.camera2.CameraCharacteristics 6 | import android.os.Build 7 | import android.support.annotation.RequiresApi 8 | import co.infinum.goldeneye.config.BaseSizeConfig 9 | import co.infinum.goldeneye.config.CameraInfo 10 | import co.infinum.goldeneye.config.VideoConfig 11 | import co.infinum.goldeneye.models.CameraProperty 12 | import co.infinum.goldeneye.models.toInternalSize 13 | 14 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 15 | internal class SizeConfigImpl( 16 | cameraInfo: CameraInfo, 17 | videoConfig: VideoConfig, 18 | onUpdateCallback: (CameraProperty) -> Unit 19 | ) : BaseSizeConfig(cameraInfo, videoConfig, onUpdateCallback) { 20 | 21 | override val supportedPreviewSizes by lazy { 22 | characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 23 | ?.getOutputSizes(SurfaceTexture::class.java) 24 | ?.map { it.toInternalSize() } 25 | /* Preview sizes that are too big can crash the camera. Filter only 1080p and below to keep it in order. */ 26 | ?.filter { it.isOver1080p().not() } 27 | ?.sorted() 28 | ?: emptyList() 29 | } 30 | 31 | override val supportedPictureSizes by lazy { 32 | characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 33 | ?.getOutputSizes(ImageFormat.JPEG) 34 | ?.map { it.toInternalSize() } 35 | ?.sorted() 36 | ?: emptyList() 37 | } 38 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/VideoConfigImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | import android.media.CamcorderProfile 5 | import android.os.Build 6 | import android.support.annotation.RequiresApi 7 | import co.infinum.goldeneye.config.BaseVideoConfig 8 | import co.infinum.goldeneye.models.CameraProperty 9 | import co.infinum.goldeneye.models.VideoQuality 10 | 11 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 12 | internal class VideoConfigImpl( 13 | private val id: String, 14 | onUpdateCallback: (CameraProperty) -> Unit 15 | ) : BaseVideoConfig(onUpdateCallback) { 16 | 17 | override val isVideoStabilizationSupported: Boolean by lazy { 18 | supportedVideoStabilizationModes.size > 1 19 | } 20 | 21 | private val supportedVideoStabilizationModes: List by lazy { 22 | characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES)?.toList() ?: emptyList() 23 | } 24 | 25 | override val supportedVideoQualities: List by lazy { 26 | val id = this.id.toIntOrNull() ?: return@lazy emptyList() 27 | VideoQuality.values().filter { CamcorderProfile.hasProfile(id, it.key) && it != VideoQuality.UNKNOWN } 28 | } 29 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/config/camera2/ZoomConfigImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.config.camera2 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | import co.infinum.goldeneye.OnZoomChangedCallback 7 | import co.infinum.goldeneye.config.BaseZoomConfig 8 | import co.infinum.goldeneye.models.CameraProperty 9 | import co.infinum.goldeneye.utils.LogDelegate 10 | 11 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 12 | internal class ZoomConfigImpl( 13 | onUpdateCallback: (CameraProperty) -> Unit, 14 | onZoomChangedCallback: OnZoomChangedCallback? 15 | ) : BaseZoomConfig(onUpdateCallback, onZoomChangedCallback) { 16 | 17 | override var zoom = 100 18 | set(value) { 19 | if (isZoomSupported) { 20 | field = value.coerceIn(100, maxZoom) 21 | onUpdateCallback(CameraProperty.ZOOM) 22 | onZoomChangedCallback?.onZoomChanged(value) 23 | } else { 24 | LogDelegate.log("Unsupported ZoomLevel [$value]") 25 | } 26 | } 27 | 28 | override val maxZoom: Int by lazy { 29 | ((characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1f) * 100).toInt() 30 | } 31 | 32 | override val isZoomSupported: Boolean by lazy { 33 | maxZoom != 100 34 | } 35 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/Any.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import co.infinum.goldeneye.utils.AsyncUtils 6 | 7 | internal val MAIN_HANDLER = Handler(Looper.getMainLooper()) 8 | 9 | internal fun ifNotNull(p1: T1?, p2: T2?, action: (T1, T2) -> Unit) { 10 | if (p1 != null && p2 != null) { 11 | action(p1, p2) 12 | } 13 | } 14 | 15 | /** 16 | * Run task on background thread and return nullable result on main thread. 17 | * 18 | * @param task that you want to execute on background thread. Return null in 19 | * case of error. 20 | * @param onResult function receives [task] result and handles it on the 21 | * main thread. 22 | */ 23 | internal fun async(task: () -> T?, onResult: (T?) -> Unit) { 24 | AsyncUtils.backgroundHandler.post { 25 | val result = task() 26 | MAIN_HANDLER.post { onResult(result) } 27 | } 28 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/Bitmap.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("BitmapUtils") 2 | 3 | package co.infinum.goldeneye.extensions 4 | 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.graphics.Matrix 8 | import android.media.Image 9 | import android.os.Build 10 | import android.support.annotation.RequiresApi 11 | import co.infinum.goldeneye.utils.Intrinsics 12 | 13 | /** 14 | * Directly apply new matrix to bitmap and return new Bitmap. 15 | * Old bitmap is recycled to clear memory. 16 | */ 17 | internal fun Bitmap.applyMatrix(configure: Matrix.() -> Unit): Bitmap { 18 | Intrinsics.checkMainThread() 19 | val newBitmap = Bitmap.createBitmap(this, 0, 0, width, height, Matrix().apply(configure), false) 20 | safeRecycle(newBitmap) 21 | return newBitmap 22 | } 23 | 24 | internal fun Bitmap.safeRecycle(newBitmap: Bitmap) { 25 | if (this != newBitmap) { 26 | recycle() 27 | } 28 | } 29 | 30 | internal fun ByteArray.toBitmap() = 31 | try { 32 | BitmapFactory.decodeByteArray(this, 0, size) 33 | } catch (t: Throwable) { 34 | null 35 | } 36 | 37 | /** 38 | * Convert Camera2 API [Image] to [Bitmap] 39 | */ 40 | @RequiresApi(Build.VERSION_CODES.KITKAT) 41 | internal fun Image.toBitmap(): Bitmap? { 42 | val buffer = planes[0].buffer 43 | val byteArray = ByteArray(buffer.remaining()) 44 | buffer.get(byteArray) 45 | return byteArray.toBitmap() 46 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/Camera.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.extensions 4 | 5 | import android.hardware.Camera 6 | import co.infinum.goldeneye.utils.LogDelegate.log 7 | 8 | /** 9 | * Batch update camera parameters and apply them to Camera instantly. 10 | */ 11 | internal fun Camera.updateParams(update: Camera.Parameters.() -> Unit) { 12 | try { 13 | parameters = parameters?.apply(update) 14 | } catch (t: Throwable) { 15 | log("Failed to update Camera properties.", t) 16 | } 17 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/CaptureRequest.Builder.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.hardware.camera2.CaptureRequest 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | import co.infinum.goldeneye.config.camera2.Camera2ConfigImpl 7 | import co.infinum.goldeneye.models.FlashMode 8 | import co.infinum.goldeneye.utils.CameraUtils 9 | 10 | /** 11 | * Copy given request builder parameters to [this] request builder. 12 | */ 13 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 14 | internal fun CaptureRequest.Builder?.copyParamsFrom(other: CaptureRequest.Builder?) { 15 | if (other == null || this == null) { 16 | return 17 | } 18 | set(CaptureRequest.CONTROL_AF_MODE, other[CaptureRequest.CONTROL_AF_MODE]) 19 | set(CaptureRequest.CONTROL_EFFECT_MODE, other[CaptureRequest.CONTROL_EFFECT_MODE]) 20 | set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, other[CaptureRequest.CONTROL_AE_ANTIBANDING_MODE]) 21 | set(CaptureRequest.CONTROL_AWB_MODE, other[CaptureRequest.CONTROL_AWB_MODE]) 22 | set(CaptureRequest.SCALER_CROP_REGION, other[CaptureRequest.SCALER_CROP_REGION]) 23 | set(CaptureRequest.CONTROL_AE_MODE, other[CaptureRequest.CONTROL_AE_MODE]) 24 | set(CaptureRequest.FLASH_MODE, other[CaptureRequest.FLASH_MODE]) 25 | } 26 | 27 | /** 28 | * Create new capture request builder from given config. 29 | */ 30 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 31 | internal fun CaptureRequest.Builder?.applyConfig(config: Camera2ConfigImpl?) { 32 | if (this == null || config == null) { 33 | return 34 | } 35 | 36 | with(config) { 37 | if (supportedFocusModes.contains(focusMode)) { 38 | set(CaptureRequest.CONTROL_AF_MODE, focusMode.toCamera2()) 39 | } 40 | 41 | if (supportedColorEffectModes.contains(colorEffectMode)) { 42 | set(CaptureRequest.CONTROL_EFFECT_MODE, colorEffectMode.toCamera2()) 43 | } 44 | 45 | if (supportedAntibandingModes.contains(antibandingMode)) { 46 | set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, antibandingMode.toCamera2()) 47 | } 48 | 49 | if (supportedWhiteBalanceModes.contains(whiteBalanceMode)) { 50 | set(CaptureRequest.CONTROL_AWB_MODE, whiteBalanceMode.toCamera2()) 51 | } 52 | 53 | if (config.zoom > 100) { 54 | val zoomRect = CameraUtils.calculateCamera2ZoomRect(this) 55 | if (zoomRect != null) { 56 | set(CaptureRequest.SCALER_CROP_REGION, zoomRect) 57 | } 58 | } else { 59 | set(CaptureRequest.SCALER_CROP_REGION, null) 60 | } 61 | 62 | if (videoStabilizationEnabled) { 63 | set( 64 | CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 65 | if (videoStabilizationEnabled) { 66 | CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON 67 | } else { 68 | CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF 69 | } 70 | ) 71 | } 72 | 73 | /* Awesome Camera2 API workaround to support FlashMode.TORCH */ 74 | if (supportedFlashModes.contains(flashMode)) { 75 | if (flashMode == FlashMode.TORCH) { 76 | set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) 77 | set(CaptureRequest.FLASH_MODE, FlashMode.TORCH.toCamera2()) 78 | } else { 79 | set(CaptureRequest.CONTROL_AE_MODE, flashMode.toCamera2()) 80 | set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF) 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/CaptureResult.kt: -------------------------------------------------------------------------------- 1 | @file:RequiresApi(Build.VERSION_CODES.LOLLIPOP) 2 | 3 | package co.infinum.goldeneye.extensions 4 | 5 | import android.hardware.camera2.CaptureResult 6 | import android.os.Build 7 | import android.support.annotation.RequiresApi 8 | 9 | fun CaptureResult?.isLocked() = isFocusReady() && isExposureReady() 10 | 11 | private fun CaptureResult?.isExposureReady(): Boolean { 12 | if (this == null) return false 13 | 14 | val aeMode = get(CaptureResult.CONTROL_AE_MODE) 15 | val aeState = get(CaptureResult.CONTROL_AE_STATE) 16 | 17 | return aeMode == CaptureResult.CONTROL_AE_MODE_OFF 18 | || aeState == null 19 | || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED 20 | || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED 21 | || aeState == CaptureResult.CONTROL_AE_STATE_LOCKED 22 | } 23 | 24 | fun CaptureResult?.isFocusReady(): Boolean { 25 | if (this == null) return false 26 | 27 | val afState = get(CaptureResult.CONTROL_AF_STATE) 28 | val afMode = get(CaptureResult.CONTROL_AF_MODE) 29 | 30 | return afMode == CaptureResult.CONTROL_AF_MODE_OFF 31 | || afState == CaptureResult.CONTROL_AF_STATE_INACTIVE 32 | || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED 33 | || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED 34 | || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED 35 | || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED 36 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/Context.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.support.v4.app.ActivityCompat 7 | 8 | internal fun Context.hasCameraPermission() = hasPermission(Manifest.permission.CAMERA) 9 | internal fun Context.hasAudioPermission() = hasPermission(Manifest.permission.RECORD_AUDIO) 10 | 11 | private fun Context.hasPermission(permission: String) = 12 | ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/Matrix.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.graphics.Matrix 4 | 5 | internal fun Matrix.rotate(degrees: Float, cx: Float, cy: Float) = apply { postRotate(degrees, cx, cy) } 6 | 7 | internal fun Matrix.mirror() = apply { postScale(-1f, 1f) } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/MediaRecorder.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.extensions 4 | 5 | import android.app.Activity 6 | import android.hardware.Camera 7 | import android.media.CamcorderProfile 8 | import android.media.MediaCodec 9 | import android.media.MediaRecorder 10 | import android.os.Build 11 | import android.support.annotation.RequiresApi 12 | import co.infinum.goldeneye.config.CameraConfig 13 | import co.infinum.goldeneye.models.Facing 14 | import co.infinum.goldeneye.utils.CameraUtils 15 | import java.io.File 16 | 17 | internal fun MediaRecorder.buildCamera1Instance( 18 | activity: Activity, 19 | camera: Camera, 20 | config: CameraConfig, 21 | file: File 22 | ): MediaRecorder { 23 | setCamera(camera) 24 | setVideoSource(MediaRecorder.VideoSource.CAMERA) 25 | return buildInstance(activity, config, file) 26 | } 27 | 28 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 29 | internal fun MediaRecorder.buildCamera2Instance( 30 | activity: Activity, 31 | config: CameraConfig, 32 | file: File 33 | ): MediaRecorder { 34 | setVideoSource(MediaRecorder.VideoSource.SURFACE) 35 | return buildInstance(activity, config, file) 36 | } 37 | 38 | /** 39 | * Reusable build instance method to apply given config to [MediaRecorder]. 40 | */ 41 | private fun MediaRecorder.buildInstance(activity: Activity, config: CameraConfig, file: File): MediaRecorder { 42 | val profile = CamcorderProfile.get(config.id.toInt(), config.videoQuality.key) 43 | if (activity.hasAudioPermission()) { 44 | setAudioSource(MediaRecorder.AudioSource.DEFAULT) 45 | } 46 | setOutputFormat(profile.fileFormat) 47 | setVideoFrameRate(profile.videoFrameRate) 48 | setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight) 49 | setVideoEncodingBitRate(profile.videoBitRate) 50 | setVideoEncoder(profile.videoCodec) 51 | 52 | if (activity.hasAudioPermission()) { 53 | setAudioEncodingBitRate(profile.audioBitRate) 54 | setAudioChannels(profile.audioChannels) 55 | setAudioSamplingRate(profile.audioSampleRate) 56 | setAudioEncoder(profile.audioCodec) 57 | } 58 | 59 | setOutputFile(file.absolutePath) 60 | val cameraOrientation = CameraUtils.calculateDisplayOrientation(activity, config) 61 | setOrientationHint(if (config.facing == Facing.FRONT) (360 - cameraOrientation) % 360 else cameraOrientation) 62 | prepare() 63 | return this 64 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/TextureView.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.graphics.SurfaceTexture 4 | import android.view.TextureView 5 | 6 | internal fun TextureView.onSurfaceUpdate(onAvailable: (TextureView) -> Unit, onSizeChanged: (TextureView) -> Unit) { 7 | if (isAvailable) { 8 | onAvailable(this) 9 | } 10 | 11 | surfaceTextureListener = object : TextureView.SurfaceTextureListener { 12 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) { 13 | onSizeChanged(this@onSurfaceUpdate) 14 | } 15 | 16 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { 17 | onAvailable(this@onSurfaceUpdate) 18 | } 19 | 20 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) { 21 | } 22 | 23 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = false 24 | } 25 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/extensions/View.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.extensions 2 | 3 | import android.view.View 4 | 5 | internal fun View.isMeasured() = height > 0 && width > 0 6 | internal fun View.isNotMeasured() = isMeasured().not() -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/FocusHandler.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.gesture 2 | 3 | import android.graphics.PointF 4 | 5 | /** 6 | * Reusable interface used in [GestureManager] to 7 | * support multiple implementations. 8 | */ 9 | internal interface FocusHandler { 10 | fun requestFocus(point: PointF) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/GestureManager.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.gesture 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.graphics.PointF 6 | import android.view.GestureDetector 7 | import android.view.MotionEvent 8 | import android.view.ScaleGestureDetector 9 | import android.view.TextureView 10 | import co.infinum.goldeneye.extensions.MAIN_HANDLER 11 | 12 | /** 13 | * Delegate class that encapsulates gesture handling - pinch to zoom and tap to focus. 14 | * 15 | * It detects those gestures and then dispatches event to [FocusHandler] or [ZoomHandler]. 16 | * 17 | * @see ZoomHandler 18 | * @see FocusHandler 19 | */ 20 | @SuppressLint("ClickableViewAccessibility") 21 | internal class GestureManager( 22 | activity: Activity, 23 | textureView: TextureView, 24 | private val zoomHandler: ZoomHandler, 25 | private val focusHandler: FocusHandler 26 | ) { 27 | 28 | private val pinchDetector = ScaleGestureDetector(activity, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { 29 | 30 | override fun onScaleEnd(detector: ScaleGestureDetector?) { 31 | zoomHandler.onPinchEnded() 32 | } 33 | 34 | override fun onScale(detector: ScaleGestureDetector?): Boolean { 35 | val pinchDelta = detector?.let { it.currentSpan - it.previousSpan } ?: 0f 36 | zoomHandler.onPinchStarted(pinchDelta) 37 | return true 38 | } 39 | }) 40 | 41 | private val tapDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() { 42 | override fun onSingleTapUp(e: MotionEvent?): Boolean { 43 | if (e == null) return false 44 | focusHandler.requestFocus(PointF(e.x, e.y)) 45 | return true 46 | } 47 | 48 | override fun onDown(e: MotionEvent?): Boolean { 49 | return true 50 | } 51 | }) 52 | 53 | init { 54 | textureView.setOnTouchListener { _, event -> 55 | tapDetector.onTouchEvent(event) 56 | pinchDetector.onTouchEvent(event) 57 | true 58 | } 59 | } 60 | 61 | fun release() { 62 | MAIN_HANDLER.removeCallbacksAndMessages(null) 63 | } 64 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/ZoomHandler.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.gesture 2 | 3 | /** 4 | * Reusable interface used in [GestureManager] to 5 | * support multiple implementations. 6 | */ 7 | internal interface ZoomHandler { 8 | fun onPinchStarted(pinchDelta: Float) 9 | fun onPinchEnded() 10 | } 11 | 12 | -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/ZoomHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.gesture 2 | 3 | import android.app.Activity 4 | import android.hardware.camera2.CaptureRequest 5 | import android.util.TypedValue 6 | import co.infinum.goldeneye.config.CameraConfig 7 | 8 | internal class ZoomHandlerImpl( 9 | activity: Activity, 10 | private val config: CameraConfig 11 | ): ZoomHandler { 12 | 13 | private val zoomPinchDelta: Int = 14 | TypedValue.applyDimension( 15 | TypedValue.COMPLEX_UNIT_DIP, 16 | 1f, 17 | activity.resources.displayMetrics 18 | ).toInt() 19 | private var pinchDelta = 0f 20 | 21 | override fun onPinchStarted(pinchDelta: Float) { 22 | if (config.pinchToZoomEnabled.not()) { 23 | return 24 | } 25 | 26 | this.pinchDelta += pinchDelta 27 | val zoomDelta = (this.pinchDelta / (zoomPinchDelta * config.pinchToZoomFriction)).toInt() 28 | 29 | if (zoomDelta != 0) { 30 | config.zoom = (config.zoom + zoomDelta).coerceIn(100, config.maxZoom) 31 | } 32 | this.pinchDelta %= zoomPinchDelta 33 | } 34 | 35 | override fun onPinchEnded() { 36 | pinchDelta = 0f 37 | } 38 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/camera1/FocusHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.gesture.camera1 4 | 5 | import android.app.Activity 6 | import android.graphics.Point 7 | import android.graphics.PointF 8 | import android.hardware.Camera 9 | import android.view.TextureView 10 | import co.infinum.goldeneye.config.CameraConfig 11 | import co.infinum.goldeneye.extensions.MAIN_HANDLER 12 | import co.infinum.goldeneye.extensions.updateParams 13 | import co.infinum.goldeneye.gesture.FocusHandler 14 | import co.infinum.goldeneye.models.FocusMode 15 | import co.infinum.goldeneye.utils.CameraUtils 16 | 17 | internal class FocusHandlerImpl( 18 | private val activity: Activity, 19 | private val camera: Camera, 20 | private val textureView: TextureView, 21 | private val config: CameraConfig, 22 | private val onFocusChanged: (Point) -> Unit 23 | ) : FocusHandler { 24 | 25 | override fun requestFocus(point: PointF) { 26 | if (config.tapToFocusEnabled.not() || config.supportedFocusModes.contains(FocusMode.AUTO).not()) return 27 | 28 | camera.updateParams { 29 | /* When applying tap to focus, change focus to AUTO */ 30 | focusMode = FocusMode.AUTO.toCamera1() 31 | val areas = CameraUtils.calculateCamera1FocusArea(activity, textureView, config, point.x, point.y) 32 | focusAreas = areas 33 | onFocusChanged(Point(point.x.toInt(), point.y.toInt())) 34 | } 35 | 36 | camera.autoFocus { success, _ -> 37 | if (success) { 38 | /* Reset auto focus once it is focused */ 39 | camera.cancelAutoFocus() 40 | resetFocusWithDelay() 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Reset focus to chosen focus after [CameraConfig.tapToFocusResetDelay] milliseconds. 47 | * 48 | * @see CameraConfig.tapToFocusResetDelay 49 | */ 50 | private fun resetFocusWithDelay() { 51 | MAIN_HANDLER.removeCallbacksAndMessages(null) 52 | MAIN_HANDLER.postDelayed( 53 | { camera.updateParams { focusMode = config.focusMode.toCamera1() } }, 54 | config.tapToFocusResetDelay 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/gesture/camera2/FocusHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.gesture.camera2 2 | 3 | import android.app.Activity 4 | import android.graphics.Point 5 | import android.graphics.PointF 6 | import android.os.Build 7 | import android.support.annotation.RequiresApi 8 | import android.view.TextureView 9 | import co.infinum.goldeneye.config.CameraConfig 10 | import co.infinum.goldeneye.config.camera2.Camera2ConfigImpl 11 | import co.infinum.goldeneye.extensions.MAIN_HANDLER 12 | import co.infinum.goldeneye.gesture.FocusHandler 13 | import co.infinum.goldeneye.models.FocusMode 14 | import co.infinum.goldeneye.sessions.SessionsManager 15 | import co.infinum.goldeneye.utils.CameraUtils 16 | 17 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 18 | internal class FocusHandlerImpl( 19 | private val activity: Activity, 20 | private val textureView: TextureView, 21 | private val config: Camera2ConfigImpl, 22 | private val sessionsManager: SessionsManager, 23 | private val onFocusChanged: (Point) -> Unit 24 | ) : FocusHandler { 25 | 26 | override fun requestFocus(point: PointF) { 27 | if (config.tapToFocusEnabled.not() || config.supportedFocusModes.contains(FocusMode.AUTO).not()) return 28 | 29 | val region = CameraUtils.calculateCamera2FocusArea(activity, textureView, config, point.x, point.y) 30 | if (region != null) { 31 | sessionsManager.lockFocus(region) 32 | onFocusChanged(Point(point.x.toInt(), point.y.toInt())) 33 | resetFocusWithDelay() 34 | } 35 | } 36 | 37 | /** 38 | * Reset focus to chosen focus after [CameraConfig.tapToFocusResetDelay] milliseconds. 39 | * 40 | * @see CameraConfig.tapToFocusResetDelay 41 | */ 42 | private fun resetFocusWithDelay() { 43 | MAIN_HANDLER.removeCallbacksAndMessages(null) 44 | MAIN_HANDLER.postDelayed( 45 | { sessionsManager.unlockFocus(config.focusMode) }, 46 | config.tapToFocusResetDelay 47 | ) 48 | } 49 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/AntibandingMode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.hardware.Camera 6 | import android.hardware.camera2.CameraCharacteristics 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import co.infinum.goldeneye.IllegalEnumException 10 | 11 | /** 12 | * One of the advanced features. Use [co.infinum.goldeneye.GoldenEye.Builder.withAdvancedFeatures] method 13 | * to gain access to it. 14 | */ 15 | enum class AntibandingMode { 16 | /** 17 | * @see Camera.Parameters.ANTIBANDING_AUTO 18 | * @see CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_AUTO 19 | */ 20 | AUTO, 21 | /** 22 | * @see Camera.Parameters.ANTIBANDING_50HZ 23 | * @see CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_50HZ 24 | */ 25 | HZ_50, 26 | /** 27 | * @see Camera.Parameters.ANTIBANDING_60HZ 28 | * @see CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_60HZ 29 | */ 30 | HZ_60, 31 | /** 32 | * @see Camera.Parameters.ANTIBANDING_OFF 33 | * @see CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_OFF 34 | */ 35 | OFF, 36 | UNKNOWN; 37 | 38 | fun toCamera1() = when (this) { 39 | AntibandingMode.AUTO -> Camera.Parameters.ANTIBANDING_AUTO 40 | AntibandingMode.HZ_50 -> Camera.Parameters.ANTIBANDING_50HZ 41 | AntibandingMode.HZ_60 -> Camera.Parameters.ANTIBANDING_60HZ 42 | AntibandingMode.OFF -> Camera.Parameters.ANTIBANDING_OFF 43 | else -> throw IllegalEnumException 44 | } 45 | 46 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 47 | fun toCamera2() = when (this) { 48 | AntibandingMode.AUTO -> CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_AUTO 49 | AntibandingMode.HZ_50 -> CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_50HZ 50 | AntibandingMode.HZ_60 -> CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_60HZ 51 | AntibandingMode.OFF -> CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_OFF 52 | else -> throw IllegalEnumException 53 | } 54 | 55 | companion object { 56 | fun fromCamera1(string: String?) = when (string) { 57 | Camera.Parameters.ANTIBANDING_AUTO -> AUTO 58 | Camera.Parameters.ANTIBANDING_50HZ -> HZ_50 59 | Camera.Parameters.ANTIBANDING_60HZ -> HZ_60 60 | Camera.Parameters.ANTIBANDING_OFF -> OFF 61 | else -> UNKNOWN 62 | } 63 | 64 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 65 | fun fromCamera2(int: Int?) = when (int) { 66 | CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_OFF -> OFF 67 | CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_50HZ -> HZ_50 68 | CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_60HZ -> HZ_60 69 | CameraCharacteristics.CONTROL_AE_ANTIBANDING_MODE_AUTO -> AUTO 70 | else -> UNKNOWN 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/Camera2Error.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | import android.hardware.camera2.CameraDevice 4 | 5 | /** 6 | * Camera2 error wrapper. 7 | */ 8 | internal enum class Camera2Error( 9 | val message: String 10 | ) { 11 | IN_USE("Camera already used by higher-priority camera API client"), 12 | MAX_CAMERAS_IN_USE("Camera could not open because there are too many other open cameras"), 13 | DISABLED("Camera could not be opened due to a device policy"), 14 | DEVICE("Fatal error. Camera needs to be re-opened to be used again"), 15 | HARDWARE("Hardware error"), 16 | UNKNOWN("Unknown Camera error happened"); 17 | 18 | companion object { 19 | fun fromInt(errorCode: Int) = when (errorCode) { 20 | CameraDevice.StateCallback.ERROR_CAMERA_IN_USE -> IN_USE 21 | CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE -> MAX_CAMERAS_IN_USE 22 | CameraDevice.StateCallback.ERROR_CAMERA_DISABLED -> DISABLED 23 | CameraDevice.StateCallback.ERROR_CAMERA_DEVICE -> DEVICE 24 | CameraDevice.StateCallback.ERROR_CAMERA_SERVICE -> HARDWARE 25 | else -> UNKNOWN 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/CameraApi.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | /** 4 | * Enum used to detect which camera API is used for 5 | * implementation differences. 6 | */ 7 | enum class CameraApi { 8 | CAMERA1, 9 | CAMERA2 10 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/CameraProperty.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | /** 4 | * Defines CameraProperties that can be changed. Used in config 5 | * to let Config update handler know which property is updated. 6 | */ 7 | internal enum class CameraProperty { 8 | FOCUS, 9 | FLASH, 10 | PICTURE_SIZE, 11 | PREVIEW_SIZE, 12 | WHITE_BALANCE, 13 | ZOOM, 14 | VIDEO_STABILIZATION, 15 | COLOR_EFFECT, 16 | ANTIBANDING, 17 | PREVIEW_SCALE 18 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/CameraRequest.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | import co.infinum.goldeneye.InitCallback 4 | import co.infinum.goldeneye.config.CameraInfo 5 | 6 | internal class CameraRequest( 7 | val cameraInfo: CameraInfo, 8 | val callback: InitCallback 9 | ) -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/CameraState.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | internal enum class CameraState { 4 | CLOSED, 5 | INITIALIZING, 6 | READY, 7 | ACTIVE, 8 | TAKING_PICTURE, 9 | RECORDING_VIDEO 10 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/ColorEffectMode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.hardware.Camera 6 | import android.hardware.camera2.CameraCharacteristics 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import co.infinum.goldeneye.IllegalEnumException 10 | 11 | /** 12 | * One of the advanced features. Use [co.infinum.goldeneye.GoldenEye.Builder.withAdvancedFeatures] method 13 | * to gain access to it. 14 | */ 15 | enum class ColorEffectMode { 16 | /** 17 | * @see Camera.Parameters.EFFECT_NONE 18 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_OFF 19 | */ 20 | NONE, 21 | /** 22 | * @see Camera.Parameters.EFFECT_MONO 23 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_MONO 24 | */ 25 | MONO, 26 | /** 27 | * @see Camera.Parameters.EFFECT_NEGATIVE 28 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE 29 | */ 30 | NEGATIVE, 31 | /** 32 | * @see Camera.Parameters.EFFECT_SOLARIZE 33 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE 34 | */ 35 | SOLARIZE, 36 | /** 37 | * @see Camera.Parameters.EFFECT_SEPIA 38 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA 39 | */ 40 | SEPIA, 41 | /** 42 | * @see Camera.Parameters.EFFECT_POSTERIZE 43 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE 44 | */ 45 | POSTERIZE, 46 | /** 47 | * @see Camera.Parameters.EFFECT_WHITEBOARD 48 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD 49 | */ 50 | WHITEBOARD, 51 | /** 52 | * @see Camera.Parameters.EFFECT_BLACKBOARD 53 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD 54 | */ 55 | BLACKBOARD, 56 | /** 57 | * @see Camera.Parameters.EFFECT_AQUA 58 | * @see CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA 59 | */ 60 | AQUA, 61 | UNKNOWN; 62 | 63 | fun toCamera1() = when (this) { 64 | NONE -> Camera.Parameters.EFFECT_NONE 65 | MONO -> Camera.Parameters.EFFECT_MONO 66 | NEGATIVE -> Camera.Parameters.EFFECT_NEGATIVE 67 | SOLARIZE -> Camera.Parameters.EFFECT_SOLARIZE 68 | SEPIA -> Camera.Parameters.EFFECT_SEPIA 69 | POSTERIZE -> Camera.Parameters.EFFECT_POSTERIZE 70 | WHITEBOARD -> Camera.Parameters.EFFECT_WHITEBOARD 71 | BLACKBOARD -> Camera.Parameters.EFFECT_BLACKBOARD 72 | AQUA -> Camera.Parameters.EFFECT_AQUA 73 | else -> throw IllegalEnumException 74 | } 75 | 76 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 77 | fun toCamera2() = when (this) { 78 | NONE -> CameraCharacteristics.CONTROL_EFFECT_MODE_OFF 79 | MONO -> CameraCharacteristics.CONTROL_EFFECT_MODE_MONO 80 | NEGATIVE -> CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE 81 | SOLARIZE -> CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE 82 | SEPIA -> CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA 83 | POSTERIZE -> CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE 84 | WHITEBOARD -> CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD 85 | BLACKBOARD -> CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD 86 | AQUA -> CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA 87 | else -> throw IllegalEnumException 88 | } 89 | 90 | companion object { 91 | fun fromCamera1(string: String?) = when (string) { 92 | Camera.Parameters.EFFECT_NONE -> NONE 93 | Camera.Parameters.EFFECT_MONO -> MONO 94 | Camera.Parameters.EFFECT_NEGATIVE -> NEGATIVE 95 | Camera.Parameters.EFFECT_SOLARIZE -> SOLARIZE 96 | Camera.Parameters.EFFECT_SEPIA -> SEPIA 97 | Camera.Parameters.EFFECT_POSTERIZE -> POSTERIZE 98 | Camera.Parameters.EFFECT_WHITEBOARD -> WHITEBOARD 99 | Camera.Parameters.EFFECT_BLACKBOARD -> BLACKBOARD 100 | Camera.Parameters.EFFECT_AQUA -> AQUA 101 | else -> UNKNOWN 102 | } 103 | 104 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 105 | fun fromCamera2(int: Int?) = when (int) { 106 | CameraCharacteristics.CONTROL_EFFECT_MODE_OFF -> NONE 107 | CameraCharacteristics.CONTROL_EFFECT_MODE_MONO -> MONO 108 | CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE -> NEGATIVE 109 | CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE -> SOLARIZE 110 | CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA -> SEPIA 111 | CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE -> POSTERIZE 112 | CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD -> WHITEBOARD 113 | CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD -> BLACKBOARD 114 | CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA -> AQUA 115 | else -> UNKNOWN 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/Facing.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | enum class Facing { 4 | BACK, FRONT, EXTERNAL 5 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/FlashMode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.hardware.Camera 6 | import android.hardware.camera2.CameraCharacteristics 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import co.infinum.goldeneye.IllegalEnumException 10 | 11 | enum class FlashMode { 12 | /** 13 | * Flash mode is turned off. 14 | */ 15 | OFF, 16 | /** 17 | * Flash will always be fired. 18 | */ 19 | ON, 20 | /** 21 | * Flash will be fired automatically when required. 22 | */ 23 | AUTO, 24 | /** 25 | * Constant emission of light. 26 | */ 27 | TORCH, 28 | /** 29 | * Flash will be fired in red-eye reduction mode. 30 | */ 31 | RED_EYE, 32 | UNKNOWN; 33 | 34 | fun toCamera1() = when (this) { 35 | OFF -> Camera.Parameters.FLASH_MODE_OFF 36 | ON -> Camera.Parameters.FLASH_MODE_ON 37 | AUTO -> Camera.Parameters.FLASH_MODE_AUTO 38 | TORCH -> Camera.Parameters.FLASH_MODE_TORCH 39 | RED_EYE -> Camera.Parameters.FLASH_MODE_RED_EYE 40 | else -> throw IllegalEnumException 41 | } 42 | 43 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 44 | fun toCamera2() = when (this) { 45 | ON -> CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH 46 | AUTO -> CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH 47 | RED_EYE -> CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE 48 | OFF -> CameraCharacteristics.CONTROL_AE_MODE_ON 49 | TORCH -> CameraCharacteristics.FLASH_MODE_TORCH 50 | else -> throw IllegalEnumException 51 | } 52 | 53 | companion object { 54 | fun fromCamera1(string: String?) = when (string) { 55 | Camera.Parameters.FLASH_MODE_OFF -> OFF 56 | Camera.Parameters.FLASH_MODE_ON -> ON 57 | Camera.Parameters.FLASH_MODE_AUTO -> AUTO 58 | Camera.Parameters.FLASH_MODE_TORCH -> TORCH 59 | Camera.Parameters.FLASH_MODE_RED_EYE -> RED_EYE 60 | else -> UNKNOWN 61 | } 62 | 63 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 64 | fun fromCamera2(int: Int?) = when (int) { 65 | CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH -> ON 66 | CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH -> AUTO 67 | CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE -> RED_EYE 68 | CameraCharacteristics.CONTROL_AE_MODE_ON -> OFF 69 | else -> UNKNOWN 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/FocusMode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.hardware.Camera 6 | import android.hardware.camera2.CameraCharacteristics 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import co.infinum.goldeneye.IllegalEnumException 10 | 11 | enum class FocusMode { 12 | /** 13 | * Auto-focus mode. 14 | */ 15 | AUTO, 16 | /** 17 | * Focus is set at infinity. 18 | */ 19 | INFINITY, 20 | /** 21 | * Close-up focusing mode. 22 | */ 23 | MACRO, 24 | /** 25 | * Focus is fixed. 26 | */ 27 | FIXED, 28 | /** 29 | * Extended depth of field. 30 | */ 31 | EDOF, 32 | /** 33 | * Continuous auto focus mode intended for video recording. 34 | * It has smoother change than [CONTINUOUS_PICTURE]. 35 | */ 36 | CONTINUOUS_VIDEO, 37 | /** 38 | * Continuous auto focus mode intended for picture taking. 39 | * It is more aggressive than [CONTINUOUS_VIDEO]. 40 | */ 41 | CONTINUOUS_PICTURE, 42 | UNKNOWN; 43 | 44 | fun toCamera1() = when (this) { 45 | FocusMode.AUTO -> Camera.Parameters.FOCUS_MODE_AUTO 46 | FocusMode.INFINITY -> Camera.Parameters.FOCUS_MODE_INFINITY 47 | FocusMode.MACRO -> Camera.Parameters.FOCUS_MODE_MACRO 48 | FocusMode.FIXED -> Camera.Parameters.FOCUS_MODE_FIXED 49 | FocusMode.EDOF -> Camera.Parameters.FOCUS_MODE_EDOF 50 | FocusMode.CONTINUOUS_VIDEO -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO 51 | FocusMode.CONTINUOUS_PICTURE -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE 52 | FocusMode.UNKNOWN -> throw IllegalEnumException 53 | } 54 | 55 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 56 | fun toCamera2() = when (this) { 57 | FocusMode.AUTO -> CameraCharacteristics.CONTROL_AF_MODE_AUTO 58 | FocusMode.MACRO -> CameraCharacteristics.CONTROL_AF_MODE_MACRO 59 | FocusMode.EDOF -> CameraCharacteristics.CONTROL_AF_MODE_EDOF 60 | FocusMode.CONTINUOUS_VIDEO -> CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_VIDEO 61 | FocusMode.CONTINUOUS_PICTURE -> CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE 62 | else -> throw IllegalEnumException 63 | } 64 | 65 | companion object { 66 | fun fromCamera1(string: String?) = when (string) { 67 | Camera.Parameters.FOCUS_MODE_AUTO -> AUTO 68 | Camera.Parameters.FOCUS_MODE_INFINITY -> INFINITY 69 | Camera.Parameters.FOCUS_MODE_MACRO -> MACRO 70 | Camera.Parameters.FOCUS_MODE_FIXED -> FIXED 71 | Camera.Parameters.FOCUS_MODE_EDOF -> EDOF 72 | Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO -> CONTINUOUS_VIDEO 73 | Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE -> CONTINUOUS_PICTURE 74 | else -> UNKNOWN 75 | } 76 | 77 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 78 | fun fromCamera2(int: Int?) = when (int) { 79 | CameraCharacteristics.CONTROL_AF_MODE_AUTO -> AUTO 80 | CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE -> CONTINUOUS_PICTURE 81 | CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_VIDEO -> CONTINUOUS_VIDEO 82 | CameraCharacteristics.CONTROL_AF_MODE_EDOF -> EDOF 83 | CameraCharacteristics.CONTROL_AF_MODE_MACRO -> MACRO 84 | else -> UNKNOWN 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/PreviewScale.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | /** 4 | * Defines how preview scales on the screen. 5 | * 6 | * MANUAL_* scale requires preview size to be set, otherwise the preview 7 | * will probably be distorted. 8 | * 9 | * AUTO_* scale will automatically pick best available preview size that 10 | * has same aspect ratio as currently chosen picture size or video size. 11 | * 12 | * While standard preview is active, it will scale preview by picture size and 13 | * while video recording is active, it will scale preview by video size. 14 | */ 15 | enum class PreviewScale { 16 | /** 17 | * Preview does not scale. Developer must set preview size manually. 18 | */ 19 | MANUAL, 20 | /** 21 | * Preview scales to fit both axes. Developer must set preview size manually. 22 | */ 23 | MANUAL_FIT, 24 | /** 25 | * Preview scales to fill whole view. Developer must set preview size manually. 26 | */ 27 | MANUAL_FILL, 28 | /** 29 | * Preview scales to fit both axes. Preview size is automatically chosen. 30 | */ 31 | AUTO_FIT, 32 | /** 33 | * Preview scales to fill whole view. Preview size is automatically chosen. 34 | */ 35 | AUTO_FILL 36 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/Size.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.hardware.Camera 6 | import android.os.Build 7 | import android.support.annotation.RequiresApi 8 | 9 | data class Size internal constructor( 10 | val width: Int, 11 | val height: Int 12 | ) : Comparable { 13 | companion object { 14 | val UNKNOWN = Size(0, 0) 15 | } 16 | 17 | override fun compareTo(other: Size): Int { 18 | return other.height * other.width - width * height 19 | } 20 | 21 | val aspectRatio = if (this.height != 0 && this.width != 0) this.width.toFloat() / this.height.toFloat() else -1f 22 | fun isOver1080p() = this.width > 1080 && this.height > 1080 23 | } 24 | 25 | internal fun Camera.Size.toInternalSize() = Size(width, height) 26 | 27 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 28 | internal fun android.util.Size.toInternalSize() = Size(width, height) -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/VideoQuality.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.models 2 | 3 | import android.media.CamcorderProfile 4 | import android.os.Build 5 | import android.support.annotation.RequiresApi 6 | 7 | enum class VideoQuality( 8 | val key: Int 9 | ) { 10 | /** 11 | * @see CamcorderProfile.QUALITY_LOW 12 | */ 13 | LOW(CamcorderProfile.QUALITY_LOW), 14 | /** 15 | * @see CamcorderProfile.QUALITY_HIGH 16 | */ 17 | HIGH(CamcorderProfile.QUALITY_HIGH), 18 | /** 19 | * @see CamcorderProfile.QUALITY_720P 20 | */ 21 | RESOLUTION_720P(CamcorderProfile.QUALITY_720P), 22 | /** 23 | * @see CamcorderProfile.QUALITY_1080P 24 | */ 25 | RESOLUTION_1080P(CamcorderProfile.QUALITY_1080P), 26 | /** 27 | * @see CamcorderProfile.QUALITY_2160P 28 | */ 29 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 30 | RESOLUTION_2160P(CamcorderProfile.QUALITY_2160P), 31 | /** 32 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_LOW 33 | */ 34 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 35 | HIGH_SPEED_LOW(CamcorderProfile.QUALITY_HIGH_SPEED_LOW), 36 | /** 37 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_HIGH 38 | */ 39 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 40 | HIGH_SPEED_HIGH(CamcorderProfile.QUALITY_HIGH_SPEED_HIGH), 41 | /** 42 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_480P 43 | */ 44 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 45 | HIGH_SPEED_480P(CamcorderProfile.QUALITY_HIGH_SPEED_480P), 46 | /** 47 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_720P 48 | */ 49 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 50 | HIGH_SPEED_720P(CamcorderProfile.QUALITY_HIGH_SPEED_720P), 51 | /** 52 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_1080P 53 | */ 54 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 55 | HIGH_SPEED_1080P(CamcorderProfile.QUALITY_HIGH_SPEED_1080P), 56 | /** 57 | * @see CamcorderProfile.QUALITY_HIGH_SPEED_2160P 58 | */ 59 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 60 | HIGH_SPEED_2160P(CamcorderProfile.QUALITY_HIGH_SPEED_2160P), 61 | UNKNOWN(-1); 62 | 63 | internal fun isCamera2Required() = listOf( 64 | RESOLUTION_2160P, 65 | HIGH_SPEED_LOW, 66 | HIGH_SPEED_HIGH, 67 | HIGH_SPEED_480P, 68 | HIGH_SPEED_720P, 69 | HIGH_SPEED_1080P, 70 | HIGH_SPEED_2160P 71 | ).contains(this) 72 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/models/WhiteBalanceMode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.models 4 | 5 | import android.annotation.TargetApi 6 | import android.hardware.Camera 7 | import android.hardware.camera2.CameraCharacteristics 8 | import android.os.Build 9 | import co.infinum.goldeneye.IllegalEnumException 10 | 11 | /** 12 | * One of the advanced features. Use [co.infinum.goldeneye.GoldenEye.Builder.withAdvancedFeatures] method 13 | * to gain access to it. 14 | */ 15 | enum class WhiteBalanceMode { 16 | /** 17 | * @see CameraCharacteristics.CONTROL_AWB_MODE_OFF 18 | */ 19 | OFF, 20 | /** 21 | * @see Camera.Parameters.WHITE_BALANCE_AUTO 22 | * @see CameraCharacteristics.CONTROL_AWB_MODE_AUTO 23 | */ 24 | AUTO, 25 | /** 26 | * @see Camera.Parameters.WHITE_BALANCE_INCANDESCENT 27 | * @see CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT 28 | */ 29 | INCANDESCENT, 30 | /** 31 | * @see Camera.Parameters.WHITE_BALANCE_FLUORESCENT 32 | * @see CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT 33 | */ 34 | FLUORESCENT, 35 | /** 36 | * @see Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT 37 | * @see CameraCharacteristics.CONTROL_AWB_MODE_WARM_FLUORESCENT 38 | */ 39 | WARM_FLUORESCENT, 40 | /** 41 | * @see Camera.Parameters.WHITE_BALANCE_DAYLIGHT 42 | * @see CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT 43 | */ 44 | DAYLIGHT, 45 | /** 46 | * @see Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT 47 | * @see CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT 48 | */ 49 | CLOUDY_DAYLIGHT, 50 | /** 51 | * @see Camera.Parameters.WHITE_BALANCE_TWILIGHT 52 | * @see CameraCharacteristics.CONTROL_AWB_MODE_TWILIGHT 53 | */ 54 | TWILIGHT, 55 | /** 56 | * @see Camera.Parameters.WHITE_BALANCE_SHADE 57 | * @see CameraCharacteristics.CONTROL_AWB_MODE_SHADE 58 | */ 59 | SHADE, 60 | UNKNOWN; 61 | 62 | fun toCamera1() = when (this) { 63 | AUTO -> Camera.Parameters.WHITE_BALANCE_AUTO 64 | INCANDESCENT -> Camera.Parameters.WHITE_BALANCE_INCANDESCENT 65 | FLUORESCENT -> Camera.Parameters.WHITE_BALANCE_FLUORESCENT 66 | WARM_FLUORESCENT -> Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT 67 | DAYLIGHT -> Camera.Parameters.WHITE_BALANCE_DAYLIGHT 68 | CLOUDY_DAYLIGHT -> Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT 69 | TWILIGHT -> Camera.Parameters.WHITE_BALANCE_TWILIGHT 70 | SHADE -> Camera.Parameters.WHITE_BALANCE_SHADE 71 | else -> throw IllegalEnumException 72 | } 73 | 74 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 75 | fun toCamera2() = when (this) { 76 | OFF -> CameraCharacteristics.CONTROL_AWB_MODE_OFF 77 | AUTO -> CameraCharacteristics.CONTROL_AWB_MODE_AUTO 78 | INCANDESCENT -> CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT 79 | FLUORESCENT -> CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT 80 | WARM_FLUORESCENT -> CameraCharacteristics.CONTROL_AWB_MODE_WARM_FLUORESCENT 81 | DAYLIGHT -> CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT 82 | CLOUDY_DAYLIGHT -> CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT 83 | TWILIGHT -> CameraCharacteristics.CONTROL_AWB_MODE_TWILIGHT 84 | SHADE -> CameraCharacteristics.CONTROL_AWB_MODE_SHADE 85 | else -> throw IllegalEnumException 86 | } 87 | 88 | companion object { 89 | fun fromCamera1(string: String?) = when (string) { 90 | Camera.Parameters.WHITE_BALANCE_AUTO -> AUTO 91 | Camera.Parameters.WHITE_BALANCE_INCANDESCENT -> INCANDESCENT 92 | Camera.Parameters.WHITE_BALANCE_FLUORESCENT -> FLUORESCENT 93 | Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT -> WARM_FLUORESCENT 94 | Camera.Parameters.WHITE_BALANCE_DAYLIGHT -> DAYLIGHT 95 | Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT -> CLOUDY_DAYLIGHT 96 | Camera.Parameters.WHITE_BALANCE_TWILIGHT -> TWILIGHT 97 | Camera.Parameters.WHITE_BALANCE_SHADE -> SHADE 98 | else -> UNKNOWN 99 | } 100 | 101 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 102 | fun fromCamera2(int: Int?) = when (int) { 103 | CameraCharacteristics.CONTROL_AWB_MODE_OFF -> OFF 104 | CameraCharacteristics.CONTROL_AWB_MODE_AUTO -> AUTO 105 | CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT -> INCANDESCENT 106 | CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT -> FLUORESCENT 107 | CameraCharacteristics.CONTROL_AWB_MODE_WARM_FLUORESCENT -> WARM_FLUORESCENT 108 | CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT -> DAYLIGHT 109 | CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT -> CLOUDY_DAYLIGHT 110 | CameraCharacteristics.CONTROL_AWB_MODE_TWILIGHT -> TWILIGHT 111 | CameraCharacteristics.CONTROL_AWB_MODE_SHADE -> SHADE 112 | else -> UNKNOWN 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/recorders/PictureRecorder.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.recorders 4 | 5 | import android.app.Activity 6 | import android.graphics.Bitmap 7 | import android.hardware.Camera 8 | import co.infinum.goldeneye.PictureCallback 9 | import co.infinum.goldeneye.PictureConversionException 10 | import co.infinum.goldeneye.PictureTransformation 11 | import co.infinum.goldeneye.config.CameraConfig 12 | import co.infinum.goldeneye.extensions.async 13 | import co.infinum.goldeneye.extensions.toBitmap 14 | import co.infinum.goldeneye.utils.CameraUtils 15 | import co.infinum.goldeneye.utils.LogDelegate 16 | 17 | /** 18 | * Camera1 wrapper around picture taking logic. The only reason 19 | * this is used is to not have this whole implementation inside 20 | * [co.infinum.goldeneye.GoldenEye1Impl] class. 21 | */ 22 | internal class PictureRecorder( 23 | private val activity: Activity, 24 | private val camera: Camera, 25 | private val config: CameraConfig, 26 | private val pictureTransformation: PictureTransformation? 27 | ) { 28 | 29 | private var pictureCallback: PictureCallback? = null 30 | 31 | private val onShutter: () -> Unit = { pictureCallback?.onShutter() } 32 | 33 | private val transformBitmapTask: (ByteArray?) -> Bitmap? = { 34 | try { 35 | val bitmap = it?.toBitmap() 36 | if (bitmap != null) { 37 | val orientationDifference = CameraUtils.calculateDisplayOrientation(activity, config).toFloat() 38 | pictureTransformation?.transform(bitmap, config, orientationDifference) ?: bitmap 39 | } else { 40 | null 41 | } 42 | } catch (t: Throwable) { 43 | LogDelegate.log("Failed to get picture.", t) 44 | null 45 | } 46 | } 47 | 48 | private val onResult: (Bitmap?) -> Unit = { 49 | if (it != null) { 50 | pictureCallback?.onPictureTaken(it) 51 | } else { 52 | pictureCallback?.onError(PictureConversionException) 53 | } 54 | } 55 | 56 | fun takePicture(callback: PictureCallback) { 57 | this.pictureCallback = callback 58 | try { 59 | val cameraShutterCallback = Camera.ShutterCallback { onShutter() } 60 | val cameraPictureCallback = Camera.PictureCallback { data, _ -> 61 | async( 62 | task = { transformBitmapTask(data) }, 63 | onResult = { onResult(it) } 64 | ) 65 | } 66 | 67 | camera.takePicture(cameraShutterCallback, null, cameraPictureCallback) 68 | } catch (t: Throwable) { 69 | callback.onError(t) 70 | } 71 | } 72 | 73 | fun release() { 74 | this.pictureCallback = null 75 | } 76 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/recorders/VideoRecorder.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package co.infinum.goldeneye.recorders 4 | 5 | import android.app.Activity 6 | import android.hardware.Camera 7 | import android.media.CamcorderProfile 8 | import android.media.MediaRecorder 9 | import co.infinum.goldeneye.MediaRecorderDeadException 10 | import co.infinum.goldeneye.VideoCallback 11 | import co.infinum.goldeneye.config.CameraConfig 12 | import co.infinum.goldeneye.extensions.buildCamera1Instance 13 | import co.infinum.goldeneye.extensions.hasAudioPermission 14 | import co.infinum.goldeneye.extensions.ifNotNull 15 | import co.infinum.goldeneye.extensions.updateParams 16 | import co.infinum.goldeneye.models.Facing 17 | import co.infinum.goldeneye.models.FocusMode 18 | import co.infinum.goldeneye.models.Size 19 | import co.infinum.goldeneye.utils.CameraUtils 20 | import co.infinum.goldeneye.utils.LogDelegate 21 | import java.io.File 22 | 23 | /** 24 | * Camera1 wrapper around video recording logic. The only reason 25 | * this is used is to not have this whole implementation inside 26 | * [co.infinum.goldeneye.GoldenEye1Impl] class. 27 | */ 28 | internal class VideoRecorder( 29 | private val activity: Activity, 30 | private val camera: Camera, 31 | private val config: CameraConfig 32 | ) { 33 | 34 | private var file: File? = null 35 | private var callback: VideoCallback? = null 36 | private var mediaRecorder: MediaRecorder? = null 37 | 38 | fun startRecording(file: File, callback: VideoCallback) { 39 | this.file = file 40 | this.callback = callback 41 | if (activity.hasAudioPermission().not()) { 42 | LogDelegate.log("Recording video without audio. Missing RECORD_AUDIO permission.") 43 | } 44 | try { 45 | mediaRecorder = MediaRecorder().buildCamera1Instance(activity, camera, config, file).also { 46 | it.setOnErrorListener { _, _, _ -> 47 | this.callback?.onError(MediaRecorderDeadException) 48 | } 49 | } 50 | 51 | mediaRecorder?.start() 52 | } catch (t: Throwable) { 53 | callback.onError(t) 54 | release() 55 | } 56 | } 57 | 58 | fun stopRecording() { 59 | try { 60 | mediaRecorder?.stop() 61 | ifNotNull(callback, file) { callback, file -> 62 | callback.onVideoRecorded(file) 63 | } 64 | } catch (t: Throwable) { 65 | callback?.onError(t) 66 | } finally { 67 | release() 68 | } 69 | } 70 | 71 | fun release() { 72 | try { 73 | mediaRecorder?.release() 74 | } catch (t: Throwable) { 75 | LogDelegate.log("Failed to release media recorder.", t) 76 | } finally { 77 | mediaRecorder = null 78 | callback = null 79 | file = null 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/sessions/BaseSession.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.sessions 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.graphics.Rect 6 | import android.hardware.camera2.* 7 | import android.hardware.camera2.params.MeteringRectangle 8 | import android.os.Build 9 | import android.support.annotation.CallSuper 10 | import android.support.annotation.RequiresApi 11 | import android.view.Surface 12 | import android.view.TextureView 13 | import co.infinum.goldeneye.config.camera2.Camera2ConfigImpl 14 | import co.infinum.goldeneye.extensions.isFocusReady 15 | import co.infinum.goldeneye.extensions.isLocked 16 | import co.infinum.goldeneye.models.FocusMode 17 | import co.infinum.goldeneye.utils.AsyncUtils 18 | import co.infinum.goldeneye.utils.CameraUtils 19 | import co.infinum.goldeneye.utils.LogDelegate 20 | 21 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 22 | internal abstract class BaseSession( 23 | protected val activity: Activity, 24 | protected val cameraDevice: CameraDevice, 25 | protected val config: Camera2ConfigImpl 26 | ) { 27 | 28 | protected var sessionBuilder: CaptureRequest.Builder? = null 29 | protected var session: CameraCaptureSession? = null 30 | protected var surface: Surface? = null 31 | 32 | abstract fun createSession(textureView: TextureView) 33 | 34 | /** 35 | * Apply config changes to [sessionBuilder]. 36 | */ 37 | fun updateRequest(update: CaptureRequest.Builder.() -> Unit) { 38 | try { 39 | sessionBuilder?.apply(update) 40 | } catch (t: Throwable) { 41 | LogDelegate.log("Failed to update camera parameters.", t) 42 | } 43 | } 44 | 45 | @SuppressLint("Recycle") 46 | protected fun initTextureViewSurface(textureView: TextureView) { 47 | textureView.setTransform(CameraUtils.calculateTextureMatrix(activity, textureView, config)) 48 | val texture = textureView.surfaceTexture?.apply { 49 | val previewSize = config.previewSize 50 | setDefaultBufferSize(previewSize.width, previewSize.height) 51 | } 52 | this.surface = Surface(texture) 53 | } 54 | 55 | fun startSession() { 56 | session?.setRepeatingRequest(sessionBuilder?.build()!!, null, AsyncUtils.backgroundHandler) 57 | } 58 | 59 | /** 60 | * Cancel existing focus with [CameraMetadata.CONTROL_AF_TRIGGER_CANCEL] flag. 61 | * 62 | * This method is used before locking focus with tap to focus functionality. 63 | */ 64 | fun lockFocus(region: Rect) { 65 | try { 66 | cancelFocus() 67 | val scaledRegion = scaleZoomRegion(region) 68 | sessionBuilder?.apply { 69 | set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) 70 | set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START) 71 | set( 72 | CaptureRequest.CONTROL_AF_REGIONS, 73 | arrayOf(MeteringRectangle(scaledRegion, MeteringRectangle.METERING_WEIGHT_MAX - 1)) 74 | ) 75 | } 76 | session?.stopRepeating() 77 | session?.capture(sessionBuilder?.build()!!, object : CameraCaptureSession.CaptureCallback() { 78 | override fun onCaptureCompleted(session: CameraCaptureSession?, request: CaptureRequest?, result: TotalCaptureResult?) { 79 | startSession() 80 | } 81 | }, AsyncUtils.backgroundHandler) 82 | } catch (t: Throwable) { 83 | LogDelegate.log("Failed to lock focus.", t) 84 | } 85 | } 86 | 87 | fun unlockFocus(focus: FocusMode) { 88 | try { 89 | cancelFocus() 90 | sessionBuilder?.apply { 91 | if (config.supportedFocusModes.contains(focus)) { 92 | set(CaptureRequest.CONTROL_AF_MODE, focus.toCamera2()) 93 | } 94 | set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE) 95 | } 96 | startSession() 97 | } catch (t: Throwable) { 98 | LogDelegate.log("Failed to unlock focus.", t) 99 | } 100 | } 101 | 102 | fun resetFlash() { 103 | sessionBuilder?.apply { 104 | set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF) 105 | session?.capture(build(), null, AsyncUtils.backgroundHandler) 106 | } 107 | } 108 | 109 | /** 110 | * Calculate region by zoomed ratio. If zoom is active, we must scale the 111 | * area by that zoom ratio. 112 | */ 113 | private fun scaleZoomRegion(region: Rect): Rect { 114 | val zoomedRect = sessionBuilder?.get(CaptureRequest.SCALER_CROP_REGION) ?: return region 115 | val activeRect = config.characteristics?.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE) ?: return region 116 | 117 | val scaleX = zoomedRect.width() / activeRect.width().toFloat() 118 | val scaleY = zoomedRect.height() / activeRect.height().toFloat() 119 | 120 | return Rect( 121 | (zoomedRect.left + scaleX * region.left).toInt(), 122 | (zoomedRect.top + scaleY * region.top).toInt(), 123 | (zoomedRect.left + scaleX * region.right).toInt(), 124 | (zoomedRect.top + scaleY * region.bottom).toInt() 125 | ) 126 | } 127 | 128 | private fun cancelFocus() { 129 | sessionBuilder?.apply { 130 | set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL) 131 | set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF) 132 | set(CaptureRequest.CONTROL_AF_REGIONS, null) 133 | session?.capture(build(), null, AsyncUtils.backgroundHandler) 134 | } 135 | } 136 | 137 | @CallSuper 138 | open fun release() { 139 | try { 140 | surface?.release() 141 | session?.apply { 142 | stopRepeating() 143 | abortCaptures() 144 | close() 145 | } 146 | } catch (t: Throwable) { 147 | LogDelegate.log("Failed to release session.", t) 148 | } finally { 149 | sessionBuilder = null 150 | session = null 151 | surface = null 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/sessions/PictureSession.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.sessions 2 | 3 | import android.app.Activity 4 | import android.graphics.ImageFormat 5 | import android.graphics.Rect 6 | import android.hardware.camera2.* 7 | import android.hardware.camera2.params.MeteringRectangle 8 | import android.media.ImageReader 9 | import android.os.Build 10 | import android.support.annotation.RequiresApi 11 | import android.view.TextureView 12 | import co.infinum.goldeneye.* 13 | import co.infinum.goldeneye.config.camera2.Camera2ConfigImpl 14 | import co.infinum.goldeneye.extensions.* 15 | import co.infinum.goldeneye.models.FocusMode 16 | import co.infinum.goldeneye.utils.AsyncUtils 17 | import co.infinum.goldeneye.utils.CameraUtils 18 | import co.infinum.goldeneye.utils.LogDelegate 19 | import kotlin.math.max 20 | import kotlin.math.min 21 | 22 | /** 23 | * Class handles Preview and Picture capturing session. 24 | * 25 | * When preview session is created, [imageReader] is initialized which is later 26 | * used to capture picture. Session properties can be updated dynamically 27 | * except preview and picture size. If any of those is updated, session 28 | * must be recreated. 29 | * 30 | * Steps to take picture are: 31 | * 1) Lock focus and trigger AF and AE with 32 | * [CaptureRequest.CONTROL_AF_TRIGGER_START], [CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START] 33 | * 34 | * 2) As soon as AF and AE is triggered, flags must be reset 35 | * 36 | * 3) After first callback is received in [captureCallback], repeat capture request until 37 | * AF and AE are both ready. Once they are ready, trigger picture capture. 38 | * 39 | * 4) To capture picture create new request. New request MUST contain [imageReader] surface! 40 | * After capture is successful, [imageReader] will receive callback. Fetch bitmap from the 41 | * callback and voila. 42 | * 43 | * 5) Restart preview session 44 | * 45 | * @see ImageReader 46 | */ 47 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 48 | internal class PictureSession( 49 | activity: Activity, 50 | cameraDevice: CameraDevice, 51 | config: Camera2ConfigImpl, 52 | private val pictureTransformation: PictureTransformation? 53 | ) : BaseSession(activity, cameraDevice, config) { 54 | companion object { 55 | private const val MAX_CAPTURE_TIMES = 15 56 | } 57 | 58 | private var imageReader: ImageReader? = null 59 | private var pictureCallback: PictureCallback? = null 60 | private var initCallback: InitCallback? = null 61 | private var locked = false 62 | private var captureTimes = 0 63 | 64 | private val captureCallback = object : CameraCaptureSession.CaptureCallback() { 65 | override fun onCaptureCompleted(session: CameraCaptureSession?, request: CaptureRequest?, result: TotalCaptureResult?) { 66 | if (result != null) { 67 | process(result) 68 | } 69 | } 70 | 71 | private fun process(result: CaptureResult) { 72 | try { 73 | if (locked) return 74 | 75 | /* Wait for all states to be ready, if they are not ready repeat basic capture while camera is preparing for capture */ 76 | if (result.isLocked() || captureTimes > MAX_CAPTURE_TIMES) { 77 | /* Take picture */ 78 | locked = true 79 | capture() 80 | } else { 81 | /* Wait while camera is preparing */ 82 | captureTimes++ 83 | session?.capture(sessionBuilder?.build()!!, this, AsyncUtils.backgroundHandler) 84 | } 85 | } catch (t: Throwable) { 86 | LogDelegate.log("Failed to take picture.", t) 87 | pictureCallback?.onError(t) 88 | } 89 | } 90 | 91 | private fun capture() { 92 | cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply { 93 | copyParamsFrom(sessionBuilder) 94 | /* Set picture quality */ 95 | set(CaptureRequest.JPEG_QUALITY, config.pictureQuality.toByte()) 96 | /* Add surface target that will receive capture */ 97 | addTarget(imageReader?.surface!!) 98 | session?.apply { 99 | /* Freeze preview session */ 100 | stopRepeating() 101 | /* Take dat picture */ 102 | capture(build(), null, AsyncUtils.backgroundHandler) 103 | } 104 | } 105 | } 106 | } 107 | 108 | private val stateCallback = object : CameraCaptureSession.StateCallback() { 109 | override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { 110 | session = cameraCaptureSession 111 | try { 112 | sessionBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { 113 | applyConfig(config) 114 | addTarget(surface!!) 115 | } 116 | startSession() 117 | initCallback?.onActive() 118 | initCallback = null 119 | } catch (t: Throwable) { 120 | LogDelegate.log("Failed to open camera preview.", t) 121 | initCallback?.onError(t) 122 | initCallback = null 123 | } 124 | } 125 | 126 | override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) { 127 | LogDelegate.log("Failed to configure camera.", CameraConfigurationFailedException) 128 | initCallback?.onError(CameraConfigurationFailedException) 129 | initCallback = null 130 | } 131 | } 132 | 133 | fun createInitialPreviewSession(textureView: TextureView, callback: InitCallback) { 134 | this.initCallback = callback 135 | createSession(textureView) 136 | } 137 | 138 | override fun createSession(textureView: TextureView) { 139 | try { 140 | initTextureViewSurface(textureView) 141 | initImageReader() 142 | cameraDevice.createCaptureSession(listOf(surface, imageReader?.surface), stateCallback, AsyncUtils.backgroundHandler) 143 | } catch (t: Throwable) { 144 | LogDelegate.log("Failed to create session.", t) 145 | initCallback?.onError(t) 146 | initCallback = null 147 | } 148 | } 149 | 150 | private fun initImageReader() { 151 | this.imageReader = ImageReader.newInstance(config.pictureSize.width, config.pictureSize.height, ImageFormat.JPEG, 2) 152 | imageReader?.setOnImageAvailableListener({ reader -> 153 | async( 154 | task = { 155 | val image = reader.acquireLatestImage() 156 | val bitmap = image.toBitmap() 157 | image.close() 158 | if (bitmap != null) { 159 | val orientationDifference = CameraUtils.calculateDisplayOrientation(activity, config).toFloat() 160 | pictureTransformation?.transform(bitmap, config, orientationDifference) ?: bitmap 161 | } else { 162 | null 163 | } 164 | }, 165 | onResult = { 166 | locked = false 167 | unlockFocus(config.focusMode) 168 | if (it != null) { 169 | pictureCallback?.onPictureTaken(it) 170 | } else { 171 | pictureCallback?.onError(PictureConversionException) 172 | } 173 | } 174 | ) 175 | }, AsyncUtils.backgroundHandler) 176 | } 177 | 178 | fun takePicture(callback: PictureCallback) { 179 | captureTimes = 0 180 | this.pictureCallback = callback 181 | sessionBuilder?.apply { 182 | /* Trigger AF and AE */ 183 | if (config.focusMode in arrayOf(FocusMode.AUTO, FocusMode.CONTINUOUS_PICTURE, FocusMode.CONTINUOUS_VIDEO)) { 184 | set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) 185 | focusCenterIfUnfocused() 186 | } 187 | set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START) 188 | if (config.isHardwareAtLeastLimited()) { 189 | set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) 190 | } 191 | session?.capture(build(), captureCallback, AsyncUtils.backgroundHandler) 192 | set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE) 193 | if (config.isHardwareAtLeastLimited()) { 194 | set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE) 195 | } 196 | } 197 | } 198 | 199 | private fun focusCenterIfUnfocused() { 200 | if (isFocused()) return 201 | 202 | val activeRect = config.characteristics?.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE) ?: return 203 | val centerX = activeRect.width() / 2 204 | val centerY = activeRect.height() / 2 205 | val offsetX = (activeRect.width() * 0.1f).toInt() 206 | val offsetY = (activeRect.height() * 0.1f).toInt() 207 | val centerRegion = Rect( 208 | max(centerX - offsetX, 0), 209 | max(centerY - offsetY, 0), 210 | min(centerX + offsetX, activeRect.width()), 211 | min(centerY + offsetY, activeRect.height()) 212 | ) 213 | sessionBuilder?.set( 214 | CaptureRequest.CONTROL_AF_REGIONS, 215 | arrayOf(MeteringRectangle(centerRegion, MeteringRectangle.METERING_WEIGHT_MAX - 1)) 216 | ) 217 | } 218 | 219 | private fun isFocused(): Boolean { 220 | val regions = sessionBuilder?.get(CaptureRequest.CONTROL_AF_REGIONS) 221 | val focusedRegion = regions?.getOrNull(0) 222 | return focusedRegion?.meteringWeight == MeteringRectangle.METERING_WEIGHT_MAX - 1 223 | } 224 | 225 | override fun release() { 226 | super.release() 227 | surface = null 228 | pictureCallback = null 229 | initCallback = null 230 | try { 231 | imageReader?.close() 232 | } catch (t: Throwable) { 233 | LogDelegate.log("Failed to release picture session.", t) 234 | } finally { 235 | imageReader = null 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/sessions/SessionsManager.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.sessions 2 | 3 | import android.graphics.Rect 4 | import android.hardware.camera2.CameraMetadata 5 | import android.hardware.camera2.CaptureRequest 6 | import android.hardware.camera2.params.MeteringRectangle 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import android.view.TextureView 10 | import co.infinum.goldeneye.InitCallback 11 | import co.infinum.goldeneye.PictureCallback 12 | import co.infinum.goldeneye.VideoCallback 13 | import co.infinum.goldeneye.models.FocusMode 14 | import co.infinum.goldeneye.utils.LogDelegate 15 | import java.io.File 16 | 17 | /** 18 | * Picture and Video session wrapper. Delegates calls to picture 19 | * or video session depending on current camera state. 20 | */ 21 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 22 | internal class SessionsManager( 23 | val textureView: TextureView, 24 | private val pictureSession: PictureSession, 25 | private val videoSession: VideoSession 26 | ) { 27 | 28 | private var activeSession: BaseSession = pictureSession 29 | 30 | /** 31 | * Update both session parameters and apply them only to currently 32 | * active session. 33 | */ 34 | fun updateSession(update: CaptureRequest.Builder.() -> Unit) { 35 | pictureSession.updateRequest(update) 36 | videoSession.updateRequest(update) 37 | 38 | try { 39 | activeSession.startSession() 40 | } catch (t: Throwable) { 41 | LogDelegate.log("Failed to apply new parameters to camera.", t) 42 | } 43 | } 44 | 45 | /** 46 | * Restart session only if session is [PictureSession]. 47 | * 48 | * This can happen if preview or picture size is updated. 49 | * If Video session is active, we want to completely ignore 50 | * that update and apply it only after recording is finished. 51 | */ 52 | fun restartSession() { 53 | try { 54 | if (activeSession is PictureSession) { 55 | activeSession.createSession(textureView) 56 | } 57 | } catch (t: Throwable) { 58 | LogDelegate.log("Failed to restart session.", t) 59 | } 60 | } 61 | 62 | fun resetFlashMode() { 63 | activeSession.resetFlash() 64 | } 65 | 66 | /** 67 | * Used to lock focus for tap to focus functionality. 68 | */ 69 | fun lockFocus(region: Rect) { 70 | activeSession.lockFocus(region) 71 | } 72 | 73 | /** 74 | * Used to unlock focus after tap to focus is finished. 75 | */ 76 | fun unlockFocus(focus: FocusMode) { 77 | activeSession.unlockFocus(focus) 78 | } 79 | 80 | fun startPreview(callback: InitCallback) { 81 | activeSession = pictureSession 82 | pictureSession.createInitialPreviewSession(textureView, callback) 83 | } 84 | 85 | fun takePicture(callback: PictureCallback) { 86 | pictureSession.takePicture(callback) 87 | } 88 | 89 | fun startRecording(file: File, callback: VideoCallback) { 90 | pictureSession.release() 91 | activeSession = videoSession 92 | videoSession.startRecording(textureView, file, callback) 93 | } 94 | 95 | fun stopRecording() { 96 | videoSession.stopRecording() 97 | videoSession.release() 98 | activeSession = pictureSession 99 | pictureSession.createSession(textureView) 100 | } 101 | 102 | fun release() { 103 | pictureSession.release() 104 | videoSession.release() 105 | } 106 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/sessions/VideoSession.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.sessions 2 | 3 | import android.app.Activity 4 | import android.hardware.camera2.CameraCaptureSession 5 | import android.hardware.camera2.CameraDevice 6 | import android.media.MediaRecorder 7 | import android.os.Build 8 | import android.support.annotation.RequiresApi 9 | import android.view.Surface 10 | import android.view.TextureView 11 | import co.infinum.goldeneye.CameraConfigurationFailedException 12 | import co.infinum.goldeneye.MediaRecorderDeadException 13 | import co.infinum.goldeneye.VideoCallback 14 | import co.infinum.goldeneye.config.camera2.Camera2ConfigImpl 15 | import co.infinum.goldeneye.extensions.applyConfig 16 | import co.infinum.goldeneye.extensions.buildCamera2Instance 17 | import co.infinum.goldeneye.extensions.hasAudioPermission 18 | import co.infinum.goldeneye.extensions.ifNotNull 19 | import co.infinum.goldeneye.utils.AsyncUtils 20 | import co.infinum.goldeneye.utils.LogDelegate 21 | import java.io.File 22 | 23 | /** 24 | * Class handles video recording session. 25 | */ 26 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 27 | internal class VideoSession( 28 | activity: Activity, 29 | cameraDevice: CameraDevice, 30 | config: Camera2ConfigImpl 31 | ) : BaseSession(activity, cameraDevice, config) { 32 | 33 | private var mediaRecorder: MediaRecorder? = null 34 | private var callback: VideoCallback? = null 35 | private var file: File? = null 36 | private var mediaSurface: Surface? = null 37 | 38 | private val stateCallback = object : CameraCaptureSession.StateCallback() { 39 | override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { 40 | session = cameraCaptureSession 41 | try { 42 | /* Create new recording request */ 43 | sessionBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply { 44 | applyConfig(config) 45 | addTarget(surface!!) 46 | /* Important to add media recorder surface as output target */ 47 | addTarget(mediaSurface!!) 48 | } 49 | /* Start recording */ 50 | session?.setRepeatingRequest(sessionBuilder?.build()!!, null, AsyncUtils.backgroundHandler) 51 | mediaRecorder?.start() 52 | } catch (t: Throwable) { 53 | callback?.onError(t) 54 | } 55 | } 56 | 57 | override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) { 58 | callback?.onError(CameraConfigurationFailedException) 59 | } 60 | } 61 | 62 | fun startRecording(textureView: TextureView, file: File, callback: VideoCallback) { 63 | this.file = file 64 | this.callback = callback 65 | try { 66 | createSession(textureView) 67 | } catch (t: Throwable) { 68 | callback.onError(t) 69 | } 70 | } 71 | 72 | fun stopRecording() { 73 | try { 74 | mediaRecorder?.stop() 75 | ifNotNull(callback, file) { callback, file -> 76 | callback.onVideoRecorded(file) 77 | } 78 | mediaRecorder?.reset() 79 | } catch (t: Throwable) { 80 | callback?.onError(t) 81 | } 82 | } 83 | 84 | private fun initMediaRecorder(file: File) { 85 | if (activity.hasAudioPermission().not()) { 86 | LogDelegate.log("Recording video without audio. Missing RECORD_AUDIO permission.") 87 | } 88 | mediaRecorder = MediaRecorder().buildCamera2Instance(activity, config, file).apply { 89 | setOnErrorListener { _, _, _ -> callback?.onError(MediaRecorderDeadException) } 90 | } 91 | } 92 | 93 | override fun createSession(textureView: TextureView) { 94 | initTextureViewSurface(textureView) 95 | initMediaRecorder(file!!) 96 | /* 97 | * mediaRecorder.getSurface() returns new surface on every getter call. 98 | * That is why it is a must to save it to a variable and reuse it. 99 | */ 100 | mediaSurface = mediaRecorder?.surface 101 | cameraDevice.createCaptureSession(listOf(surface, mediaSurface), stateCallback, AsyncUtils.backgroundHandler) 102 | } 103 | 104 | override fun release() { 105 | super.release() 106 | callback = null 107 | file = null 108 | try { 109 | mediaSurface?.release() 110 | mediaRecorder?.reset() 111 | mediaRecorder?.release() 112 | } catch (t: Throwable) { 113 | LogDelegate.log("Failed to release video session.", t) 114 | } finally { 115 | mediaRecorder = null 116 | mediaSurface = null 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/utils/AsyncUtils.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.utils 2 | 3 | import android.os.Build 4 | import android.os.Handler 5 | import android.os.HandlerThread 6 | import android.support.annotation.RequiresApi 7 | import co.infinum.goldeneye.ThreadNotStartedException 8 | 9 | /** 10 | * Background thread handler. 11 | */ 12 | @Suppress("ObjectPropertyName") 13 | internal object AsyncUtils { 14 | 15 | private var backgroundThread: HandlerThread? = null 16 | 17 | private var _backgroundHandler: Handler? = null 18 | val backgroundHandler: Handler 19 | get() = _backgroundHandler ?: throw ThreadNotStartedException 20 | 21 | fun startBackgroundThread() { 22 | if (_backgroundHandler != null) return 23 | 24 | backgroundThread = HandlerThread("GoldenEye") 25 | backgroundThread?.start() 26 | _backgroundHandler = Handler(backgroundThread?.looper) 27 | } 28 | 29 | fun stopBackgroundThread() { 30 | try { 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 32 | backgroundThread?.quitSafely() 33 | } else { 34 | backgroundThread?.quit() 35 | } 36 | backgroundThread?.join() 37 | } catch (t: Throwable) { 38 | LogDelegate.log("Failed to stop background threads." ,t) 39 | } finally { 40 | _backgroundHandler = null 41 | backgroundThread = null 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/utils/IncompatibleDevicesUtils.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.utils 2 | 3 | internal object IncompatibleDevicesUtils { 4 | 5 | fun isIncompatibleDevice(model: String) = Device.values().flatMap { it.models }.find { it.equals(model, false) } != null 6 | 7 | enum class Device( 8 | val models: List 9 | ) { 10 | ONE_PLUS_6(listOf("oneplus a6000", "oneplus a6003")) 11 | } 12 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/utils/Intrinsics.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.utils 2 | 3 | import android.content.Context 4 | import android.os.Looper 5 | import co.infinum.goldeneye.MissingCameraPermissionException 6 | import co.infinum.goldeneye.TaskOnMainThreadException 7 | import co.infinum.goldeneye.extensions.hasCameraPermission 8 | 9 | internal object Intrinsics { 10 | 11 | @Throws(MissingCameraPermissionException::class) 12 | fun checkCameraPermission(context: Context) { 13 | if (context.hasCameraPermission().not()) { 14 | throw MissingCameraPermissionException 15 | } 16 | } 17 | 18 | fun checkMainThread() { 19 | if (Looper.myLooper() == Looper.getMainLooper()) { 20 | throw TaskOnMainThreadException 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /goldeneye/src/main/java/co/infinum/goldeneye/utils/LogDelegate.kt: -------------------------------------------------------------------------------- 1 | package co.infinum.goldeneye.utils 2 | 3 | import co.infinum.goldeneye.Logger 4 | 5 | internal object LogDelegate { 6 | 7 | var logger: Logger? = null 8 | 9 | fun log(message: String) = logger?.log(message) 10 | fun log(message: String, t: Throwable) { 11 | logger?.log(message) 12 | logger?.log(t) 13 | } 14 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Android-GoldenEye/5f9a7106043b68013aa65c65f8e552f64a46e8c5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 02 14:00:41 CEST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | Goldeneye -------------------------------------------------------------------------------- /maven.gradle: -------------------------------------------------------------------------------- 1 | import com.infinum.maven.SonatypeConfiguration 2 | 3 | SonatypeConfiguration.metaClass.constructor = { -> 4 | def constructor = SonatypeConfiguration.class.getConstructor() 5 | def instance = constructor.newInstance() 6 | instance.load() 7 | instance 8 | } 9 | 10 | ext { 11 | sonatype = new SonatypeConfiguration() 12 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':goldeneye', ':example' -------------------------------------------------------------------------------- /tasks.gradle: -------------------------------------------------------------------------------- 1 | task javadocs(type: Javadoc) { 2 | source = android.sourceSets.main.java.source 3 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 4 | } 5 | 6 | afterEvaluate { 7 | javadocs.classpath += files(android.libraryVariants.collect { variant -> 8 | variant.javaCompile.classpath.files 9 | }) 10 | } 11 | 12 | task generateReadme { 13 | doFirst { 14 | def readmeFile = new File("${project.rootDir}/README.md") 15 | def content = readmeFile.text 16 | content = content.replaceAll("'co\\.infinum:goldeneye:.+?'", "'co.infinum:goldeneye:${versions.goldeneye}'") 17 | readmeFile.setText(content) 18 | } 19 | } --------------------------------------------------------------------------------