├── .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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------