Compare commits

...

202 Commits

Author SHA1 Message Date
Dionysis 4ed72421aa Bump version 2018-01-03 01:59:40 +02:00
Dionysis dddd81600a Link frame processor in builder with FA 2018-01-03 01:57:46 +02:00
Dionysis Lorentzos 2c80a903c1 Merge pull request #160 from Fotoapparat/v2
🎄 V2 🎄
2017-12-31 18:07:07 +02:00
Dionysis 3f6ecbd37e Update readme and version 2017-12-31 17:18:32 +02:00
Dionysis d9114d4824 Clean 2017-12-31 17:08:38 +02:00
Dionysis 1768d73e59 Clean 2017-12-31 15:25:58 +02:00
Dionysis 51aa9eb9dc Revert compile + update versions 2017-12-31 15:19:27 +02:00
Dionysis 1cfbd2216f Bump gradle 2017-12-31 14:30:07 +02:00
Dionysis bde4c27f8b Remove ci warnings 2017-12-30 18:39:44 +02:00
Dionysis f4b60a445a Bump version 2017-12-30 17:59:14 +02:00
Dionysis 64186ec825 Use api in gradle 2017-12-30 17:58:43 +02:00
Dionysis 96f9add77d Bump coroutines version 2017-12-30 17:37:49 +02:00
Dionysis b6a6f56e38 Bump version 2017-12-30 16:58:59 +02:00
Dionysis a694830c1c Fix issue when Samsung phones have additional focus modes than the android api ones. 2017-12-30 16:58:28 +02:00
Dionysis 352a96a13c Tasks will be retroactive actions to the currently selected camera 2017-12-30 03:07:11 +02:00
Dionysis 33c5054150 Bump version 2017-12-24 21:11:26 +02:00
Dionysis ba02497fbd Update travis 2017-12-24 21:11:03 +02:00
Dionysis 95196e046b Shutdown tasks if we have a stop() operation of FA 2017-12-24 20:54:08 +02:00
Dionysis cb00abc810 Update tools 2017-12-24 17:41:38 +02:00
Dionysis 5e9a6afff7 Fix crash that SurfaceTexture is no longer available 2017-12-24 17:41:33 +02:00
Dionysis 75d20c2f37 Resolve todos 2017-12-20 12:59:15 +02:00
Dionysis d0da61bfb8 Update version 2017-12-20 12:50:47 +02:00
Dionysis 92f099500d Accept license if needed 2017-12-20 12:41:19 +02:00
Dionysis 15b4f35555 Remove 2017-12-19 18:03:47 +02:00
Dionysis c1b293da58 Ahhh travis... 2017-12-19 17:59:38 +02:00
Dionysis 09cca52111 Get latest android tools 2017-12-19 17:41:05 +02:00
Dionysis 9d6f4cb9e8 Try accept travis licenses 2017-12-19 16:51:25 +02:00
Dionysis 2ea813a257 Update build tools 2017-12-19 16:38:00 +02:00
Dionysis 8dbe53d261 Update travis file 2017-12-19 16:23:55 +02:00
Dionysis 4bd05b679f Improve sample 2017-12-19 01:00:35 +02:00
Dionysis 01dc60df24 Fix orientation crash 2017-12-19 00:41:49 +02:00
Dionysis 2b05601405 Improve sample app 2017-12-18 23:57:06 +02:00
Dionysis 1ca2462f07 Support anti banding mode 2017-12-18 19:21:41 +02:00
Dionysis e553699335 Merge branch 'master' into v2
# Conflicts:
#	README.md
#	fotoapparat/src/main/java/io/fotoapparat/Fotoapparat.java
#	fotoapparat/src/main/java/io/fotoapparat/FotoapparatBuilder.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Capabilities.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/CameraParametersDecorator.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/ParametersConverter.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/FocusCapability.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/Characteristics.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/Parameters.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/factory/ParametersFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/provider/InitialParametersProvider.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/update/UpdateRequest.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/parameter/UpdateParametersRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v1/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/Camera2Test.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/factory/ParametersFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/parameter/UpdateParametersRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/zoom/UpdateZoomLevelRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/task/GetCapabilitiesTaskTest.java
#	sample/src/main/java/io/fotoapparat/sample/MainActivity.java
2017-12-18 18:48:07 +02:00
Dionysis Lorentzos 1d6d2d0977 Merge pull request #158 from simone-gasparini/154_add_antibanding
Added anti banding parameter
2017-12-18 17:43:01 +01:00
Simone Gasparini 94268a339a Added anti banding parameter. Test fixes. 2017-12-18 13:51:10 +01:00
Simone Gasparini 03170f6b26 Added anti banding parameter 2017-12-18 13:06:04 +01:00
Dionysis fe719e8bf3 Fix tests 2017-12-18 13:45:26 +02:00
Dionysis d7db6fa196 Support jpeg quality 2017-12-18 02:54:19 +02:00
Dionysis 29cc23e047 Advertise kotlin 2017-12-17 18:44:10 +02:00
Dionysis 89a172dbb1 new line 2017-12-17 18:42:30 +02:00
Dionysis e4acda6aea Fix link 2017-12-17 18:41:57 +02:00
Dionysis 3fc55404df Update Readme 2017-12-17 18:41:04 +02:00
Dionysis 16fb6bd94c Update Readme 2017-12-17 18:39:26 +02:00
Dionysis df59b3996e Update Readme 2017-12-17 18:36:59 +02:00
Dionysis b8e43e836a Update Readme 2017-12-17 18:36:39 +02:00
Dionysis 6e15327f6c Update Readme 2017-12-17 18:35:44 +02:00
Dionysis 6aef37b3f8 Make file to kotlin 2017-12-17 17:52:59 +02:00
Dionysis 21e4b89bf7 Update adapters to kotlin + add flowable support 2017-12-17 17:51:47 +02:00
Dionysis e3f0aa7421 Rename tag 2017-12-17 16:47:54 +02:00
Dionysis 35062675d9 Implement can select camera 2017-12-14 00:07:54 +01:00
Dionysis aff03c17c8 Implement samples in both kt & java. Add builders for helping java users 2017-12-13 23:34:18 +01:00
Dionysis b4e6db0a04 Add fileLogger() in loggers 2017-12-13 21:09:28 +01:00
Dionysis 7517702165 Init FA v2 2017-12-11 22:39:00 +01:00
Dionysis 56d32eee60 Update build gradle 2017-12-11 20:58:08 +01:00
Dionysis 132736e4f7 Remove duplicate tests 2017-12-10 21:04:40 +01:00
Dionysis 2ac387d6ff Migrate build scripts 2017-12-10 20:51:57 +01:00
Dmitry Zaitsev f4ff8b91b9 Merge pull request #147 from ezaquarii/is-started
Added Fotoapparat.isStarted() getter
2017-12-07 07:03:38 +01:00
Krzysztof Narkiewicz 7b047501a6 Added Fotoapparat.isStarted() getter 2017-12-07 05:30:22 +00:00
Dionysis 80f43fcf63 Allow null fps range and sensor sensitivity (ISO) 2017-12-06 22:25:06 +01:00
Dionysis b7417650c6 Fix null check 2017-12-06 22:06:36 +01:00
Dmitry Zaitsev e7e264f522 Merge pull request #143 from Fotoapparat/prevent_wrong_selectors
Selectors cannot be trusted, therefore we ensure their values are correct
2017-12-04 16:22:20 +01:00
Dionysis e501d0d25f Add more parameter factory tests 2017-12-04 15:18:38 +01:00
Dionysis 39765cdb07 Fix/add parameter factory tests 2017-12-04 15:12:04 +01:00
Dionysis 71480f4fa8 Rename method 2017-12-04 14:52:37 +01:00
Dionysis a6028d56f3 Selectors cannot be trusted, therefore we ensure their values are correct 2017-12-02 14:21:11 +01:00
Dionysis Lorentzos 086a2ba3d3 Updated version in README to 1.5.0 2017-12-02 01:12:31 +01:00
Dionysis db642767d2 Replace calculation with ready method 2017-12-02 00:56:55 +01:00
Dionysis a698a5bf50 Remove unused method 2017-12-02 00:56:10 +01:00
Dionysis Lorentzos a06edaebdc Merge pull request #141 from ezaquarii/size-pixels-megapixels-getter
Add Size getter for pixels and megapixels
2017-12-02 00:48:25 +01:00
Dionysis Lorentzos 18e8624779 Update Size.java
Add jdocs
2017-12-02 00:44:11 +01:00
Krzysztof Narkiewicz 37f53ed6cb Rename Size.getPixels to Size.getArea, remove megapixels getter 2017-12-01 23:06:31 +00:00
Krzysztof Narkiewicz 27ca565b3e Add Size getter for pixels and megapixels 2017-12-01 22:24:00 +00:00
Dmitry Zaitsev 7fbf4c1e5a Merge pull request #134 from pflammertsma/patch-1
Update README.md to mention FotoapparatSwitcher
2017-11-23 17:31:39 +01:00
Paul Lammertsma 9d45e0ff89 Update README.md 2017-11-22 18:01:39 +01:00
Dmitry Zaitsev 68bc88e9b7 Merge pull request #133 from ezaquarii/serializable-capabilities
Make  Size and Range serializable to pass device capabilities in Bundle
2017-11-22 16:45:47 +01:00
Krzysztof Narkiewicz 86c1a7558c Make Size and Range serializable to pass device capabilities in Bundle
Passing device capabilities in Bundle extras requires manual
transformations, as types do not support neither Serializable nor
Parcelable.

Implementing Parcelable is overkill, as those PODs are not
used in any performance-critical code.

Make all Capabilities members serializable, so they can be
passed into DialogFragment inside extras.
2017-11-21 22:54:14 +00:00
Dmitry Zaitsev 0708c795c7 Merge pull request #126 from jpribble/catch_setDisplayOrientation_exception
Catch setDisplayOrientation() RuntimeException
2017-10-30 14:44:27 +01:00
Dmitry Zaitsev b4a6df6299 Merge pull request #125 from alaeri/jpegQuality
add JpegQuality parameter
2017-10-30 14:43:05 +01:00
Emmanuel Requier 143a3ad47c Remove jpegQuality setting and filesize toast from sample 2017-10-30 11:55:12 +01:00
Emmanuel Requier 41bec5dc0a Add @IntRange from 0 to 100 to parameter jpegQuality 2017-10-30 11:52:21 +01:00
jpribble ba69cce007 Log setDisplayOrientation() exception instead of calling the CameraErrorCallback 2017-10-29 21:26:22 -04:00
jpribble 65c67ca802 Catch setDisplayOrientation() RuntimeException
Catch setDisplayOrientation() RuntimeException in UpdateOrientationRoutine and pass it to the CameraErrorCallback.
2017-10-27 13:18:13 -04:00
Emmanuel Requier 4e2c4a21fe add JpegQuality parameter 2017-10-25 18:46:54 +02:00
Dmitry Zaitsev f151194798 Merge pull request #119 from ezaquarii/aspec-ratio-selector-with-tolerance
Aspect ratio selector with tolerance
2017-10-18 12:48:35 +02:00
Krzysztof Narkiewicz c7dd5b0d0a Aspect ratio selector with tolerance
Some resolutions supported by camera CCD have aspect ratios
that deviate from 16:9 and 4:3 by a small margin (ex. 16:10).

Add tolerance to aspect ratio selector, to catch all picture
sizses that would be otherwise rejected due to strict
comparison.
2017-10-12 16:53:18 +01:00
Dmitry Zaytsev 21652d836e Updated version in README to 1.4.1 2017-10-03 11:09:31 +02:00
Dmitry Zaytsev 8e55a210c6 Auto focusing in CONTINUOUS_FOCUS mode was actually breaking the focus. We are now not auto focusing anymore in this mode. Fixes issue #96. 2017-10-03 11:04:57 +02:00
Dmitry Zaytsev 88d468b539 Updated library version in README 2017-10-02 23:32:22 +02:00
Dmitry Zaytsev 672f0d9c45 Merge branch 'master' of github.com:dmitry-zaitsev/Fotoapparat into develop 2017-10-02 23:30:48 +02:00
Dmitry Zaitsev db16b73ce8 Merge pull request #108 from ezaquarii/current-parameters
Get current camera parameters
2017-10-02 22:02:36 +02:00
Krzysztof Narkiewicz e506312235 Read parameters from Camera1 directly, add params converter 2017-10-01 08:02:52 +01:00
Krzysztof Narkiewicz 036d14995e Get current camera parameters via CurrentParametersProvider 2017-09-30 19:09:19 +01:00
Krzysztof Narkiewicz a835bb7534 Added getCurrentParameters() test 2017-09-30 18:11:26 +01:00
Krzysztof Narkiewicz 58007469fa Get current parameters proof-of-concenpt 2017-09-30 17:49:37 +01:00
Dmitry Zaytsev 668511f8b5 Frame object now does not print the contents of an array. 2017-09-26 22:37:33 +02:00
Dionysis Lorentzos 2a72b24f89 Create ISSUE_TEMPLATE.md 2017-09-24 18:49:34 +02:00
Dionysis Lorentzos ec9a81994f Merge pull request #87 from friendoye/feature/iso-params
Customize ISO
2017-09-05 18:16:41 +02:00
Nikita Novik 8b69a5c9ab Make Ranges class more documented and clearer 2017-09-05 01:52:32 +03:00
Nikita Novik d082b60f8b Add nullable annotations 2017-09-05 01:49:01 +03:00
Nikita Novik 87659051df Remove redundant sort in Ranges 2017-09-03 23:12:36 +03:00
Nikita Novik 7fc912513a Add tests for CameraParametersDecorator 2017-09-03 23:09:58 +03:00
Nikita Novik ff1eb975a0 Remove useless SelectorFunction for ISO 2017-09-03 23:02:23 +03:00
Nikita Novik 110fda0a0a Capabilities WIP 2017-09-03 23:00:40 +03:00
Nikita Novik fe36a7d05b Use CameraParametersDecorator for changing camera's parameters
It's easy to use CameraParametersDecorator instead of Camera.Parameters for setting and getting camera's parameters because only CameraParametersDecorator know how to get and set parameters for specific camera attribute (props to Camera1 API)
2017-09-03 23:00:04 +03:00
Nikita Novik c354e323b3 Expose only Camera.Parameters API instead of instance in CameraParametersProvider 2017-09-03 21:53:59 +03:00
Nikita Novik 2f2b7a0d0f Make range package more clearer 2017-09-03 21:33:17 +03:00
Nikita Novik 1348705a28 Add new API to sample project 2017-09-02 23:16:57 +03:00
Nikita Novik d1991c0dc1 Remove unnecessary check 2017-09-02 23:16:22 +03:00
Nikita Novik 25b228c211 Improve documentation 2017-09-02 23:15:47 +03:00
Nikita Novik d3391f48db Add test for StringUtils 2017-09-02 22:46:03 +03:00
Nikita Novik 627d03e2d0 Test sensorSensitivity methods 2017-09-02 22:45:46 +03:00
Nikita Novik a89c22c339 Enhance range package 2017-09-02 22:44:08 +03:00
Nikita Novik c0bf07a78d Use CameraParametersCoverter to extract camera info values 2017-09-02 19:17:39 +03:00
Nikita Novik a8614f0e9e Handle only numberic ISO values 2017-09-02 18:37:54 +03:00
Nikita Novik e3b4ee91f0 Merge remote-tracking branch 'hagabaka/master' into feature/iso-params
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/Fotoapparat.java
#	fotoapparat/src/main/java/io/fotoapparat/FotoapparatBuilder.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Capabilities.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/ParametersConverter.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/Characteristics.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/parameters/CaptureRequestFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/parameters/ParametersProvider.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/parameters/Request.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/Parameters.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/provider/InitialParametersProvider.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/range/ContinuousRange.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/range/Range.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/StartCameraRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/Camera2Test.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/StartCameraRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/task/GetCapabilitiesTaskTest.java
2017-09-02 18:29:48 +03:00
Nikita Novik 30a3f132cc Merge pull request #2 from Fotoapparat/develop
Develop
2017-09-02 01:13:59 +03:00
Dmitry Zaitsev a4fe1bd98d Merge pull request #83 from friendoye/fearure/frame-fps-params
Customize preview FPS range
2017-08-29 22:39:40 +02:00
Nikita Novik 83726252f3 Merge remote-tracking branch 'origin/master' into fearure/frame-fps-params
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Capabilities.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v1/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/Camera2Test.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/factory/ParametersFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/parameter/UpdateParametersRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/task/GetCapabilitiesTaskTest.java
2017-08-29 23:00:48 +03:00
Nikita Novik 644f7dab27 Merge pull request #1 from Fotoapparat/master
Update
2017-08-29 22:48:30 +03:00
Nikita Novik 2aa8fd620c Merge remote-tracking branch 'origin/develop' into fearure/frame-fps-params
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/parameter/provider/InitialParametersProvider.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
2017-08-29 22:31:55 +03:00
Dmitry Zaitsev 5c622714e5 Removed line about Camera2. Added other benefits. 2017-08-26 17:24:13 +02:00
Dmitry Zaytsev 0ad6dbe102 Updated version in README to 1.3.0 2017-08-26 17:08:33 +02:00
Dmitry Zaitsev c7c0173da1 Merge pull request #85 from Fotoapparat/feature/zoom
Zoom
2017-08-26 16:36:31 +02:00
Dmitry Zaytsev d32dfc72de Performance optimizations in Camera1. 2017-08-26 16:35:26 +02:00
Dmitry Zaytsev 54f5c2bb65 Added zoom seekbar to the sample app. 2017-08-26 16:22:38 +02:00
Dmitry Zaytsev 91d8819cd9 Added Capabilities caching to Camera1. 2017-08-26 16:12:06 +02:00
Dmitry Zaytsev 180c238ae2 Implemented UpdateZoomLevelRoutine. 2017-08-26 16:10:17 +02:00
Dmitry Zaytsev d0969068ed Added setZoom to Fotoapparat API. 2017-08-26 16:01:44 +02:00
Dmitry Zaytsev 7fe3db197e Added ZoomOperator which Camera1 now implements. 2017-08-26 15:53:29 +02:00
Dmitry Zaytsev ad42f3a675 Capabilities now provide information about whether zoom feature is available or not. 2017-08-26 15:44:41 +02:00
Dmitry Zaytsev da7ebf7c35 Fixed racing condition when we were waiting for SurfaceTexture even though view was already detached from window. 2017-08-26 15:35:55 +02:00
Nikita Novik 688b47517f Replace ContinuousRange instance creation with factory method 2017-08-24 03:28:55 +03:00
Nikita Novik 0687c54c5e Use more meaningful names in PreviewFpsRangeSelectors 2017-08-24 03:13:08 +03:00
Nikita Novik 051fb3a157 Add missing tests 2017-08-24 03:08:04 +03:00
Nikita Novik feaa19ec2b Use primitive type for null-safety 2017-08-24 03:04:39 +03:00
Nikita Novik 148ede7245 Return from Characteristics V2 Fotoapparat's Range 2017-08-24 03:02:37 +03:00
Nikita Novik 2a6b4f29d6 Restructure and add test for preview FPS ranges extraction method 2017-08-20 23:24:27 +03:00
Nikita Novik c120a42aa5 Get rid of IntervalRange 2017-08-20 23:08:14 +03:00
Nikita Novik cac8e18494 Add tests 2017-08-17 21:22:13 +03:00
Nikita Novik 1d575af7ab Add documentation 2017-08-17 20:04:45 +03:00
Nikita Novik 4370ff3772 Add preview FPS range parameter 2017-08-17 18:08:14 +03:00
Dmitry Zaitsev 0375a0c78d Merge pull request #75 from Fotoapparat/feature/update-configuration
Updating configuration after Fotoapparat is already started
2017-08-09 21:44:43 +02:00
Dmitry Zaitsev 96685954b1 Added part about updating parameters 2017-08-09 21:43:53 +02:00
Dmitry Zaytsev ee73135b8d Added test for InitialParametersProvider. 2017-08-09 21:39:00 +02:00
Dmitry Zaytsev 290994b83e Added test for UpdateParametersRoutine. 2017-08-09 21:07:19 +02:00
Dmitry Zaytsev 4663358edd Updated example to toggle torch 2017-08-09 01:19:35 +02:00
Dmitry Zaytsev 1df33da0c6 Implemented UpdateParametersRoutine. 2017-08-09 00:59:03 +02:00
Dmitry Zaytsev 80c290cb1f Added ParametersFactory. Rewritten InitialParametersProvider. Refactored tests. 2017-08-09 00:50:30 +02:00
Dmitry Zaytsev 3b8e8a8395 Added UpdateRequest. 2017-08-08 21:35:20 +02:00
Dmitry Zaytsev 15e4f0aa9a Merge branch 'develop' of github.com:dmitry-zaitsev/Fotoapparat into develop 2017-08-07 22:57:20 +02:00
Dmitry Zaytsev 33ea208754 Using support-annotations instead of appcompat 2017-08-07 22:56:55 +02:00
Dionysis b6ade7868a Merge branch 'develop' of github.com:Fotoapparat/Fotoapparat into develop 2017-07-16 23:25:30 +02:00
Dionysis Lorentzos 295777c622 Update README.md 2017-07-16 23:23:56 +02:00
Dionysis 42339f800c Merge branch 'develop' 2017-07-16 23:14:02 +02:00
Dmitry Zaytsev 6df93c4527 Merge branch 'master' of github.com:dmitry-zaitsev/Fotoapparat into develop 2017-07-16 22:08:36 +02:00
Dmitry Zaitsev 8d5b16c4bd Update README.md 2017-07-16 22:08:11 +02:00
Dionysis Lorentzos 7e9c31aabc Update README.md 2017-07-16 22:06:58 +02:00
Dionysis Lorentzos add2692f37 Update README.md 2017-07-16 22:03:23 +02:00
Dionysis Lorentzos 43029afab3 Merge pull request #59 from Fotoapparat/feature/preview_scaletype
Feature/preview scaletype
2017-07-16 22:01:43 +02:00
Dionysis 358bd1b7f3 Remove ScaleTypeSelectors 2017-07-16 22:00:43 +02:00
Dionysis eb0746a780 Format 2017-07-16 21:54:38 +02:00
Dionysis 58e5e93382 Merge branch 'develop' into feature/preview_scaletype
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/StartCameraRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/StartCameraRoutineTest.java
2017-07-16 21:53:07 +02:00
Dionysis 6d22e7c6ae Merge branch 'develop' into feature/preview_scaletype
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/StartCameraRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/StartCameraRoutineTest.java
2017-07-16 21:52:56 +02:00
Dionysis 0bf4a3df8f Remove redundant code 2017-07-16 21:45:02 +02:00
Dionysis 57729b9dd8 Remove traveling param and use it in the start routine 2017-07-16 21:40:16 +02:00
Dionysis f3d67273a2 Jdoc typos 2017-07-16 20:32:56 +02:00
Dionysis 0b0ded639b Update readme and example 2017-07-16 20:29:13 +02:00
Dionysis 898769b1a4 Camera 2 tests 2017-07-16 20:24:27 +02:00
Dmitry Zaitsev 0ce7f5e687 Merge pull request #58 from defer/ft-ICS-MR0
fotoapparat: Support API level 14
2017-07-16 18:59:32 +02:00
Diogo Ferreira c2b8755f5f fotoapparat: Support API level 14
15 is already supported, and 14 isn't much different. The only call that
surfaced lint errors is only used on api 21 or above for camera 2.
2017-07-16 16:12:16 +01:00
Dionysis 628781b706 Camera 1 now supports preview scale type 2017-07-16 15:40:50 +02:00
Dmitry Zaitsev 3dfa659723 Added mention about FaceDetector 2017-07-16 12:48:14 +02:00
Dionysis 37ec1addde Add scale type in the builder 2017-07-15 22:57:46 +02:00
Dionysis 6614785f7b Add scale type selectors 2017-07-15 22:54:35 +02:00
Dmitry Zaytsev e89a06e4e0 [issue-56] Ignoring CameraException when trying to restart preview after taking a photo. 2017-07-14 22:35:27 +02:00
Dmitry Zaytsev 4f46df5bfd Added example of CameraErrorHandler to sample app. 2017-07-14 22:17:48 +02:00
Dmitry Zaytsev e5c60fb31f Redirecting Camera1 errors to CameraErrorCallback instead of crashing the app. 2017-07-14 22:14:38 +02:00
Dmitry Zaytsev 88c8281f27 UnrecoverableErrorCallback can now be attached to Fotoapparat 2017-07-12 23:42:18 +02:00
Dmitry Zaytsev 4249bc5b30 Added @NonNull annotations to FotoapparatBuilder 2017-07-12 23:23:50 +02:00
Dmitry Zaytsev 541bd2d740 Updated version in README to 1.1.0 2017-07-08 23:16:06 +02:00
Dmitry Zaytsev b25faddf57 In Camera1 we now try to split parameters into individual sub-maps of size 1 in case if updating all parameters at once fails. 2017-07-08 23:15:28 +02:00
Dmitry Zaytsev 82501be283 Added Fotoapparat.focus(). 2017-07-08 22:33:14 +02:00
Dmitry Zaytsev fcf1432c43 AutoFocusRoutine now returns PendingResult of FocusResult. Cleaned up sample app. 2017-07-08 22:25:53 +02:00
Dmitry Zaytsev 79e8b58167 Added timeout for autofocus callback in Camera1 2017-07-07 19:23:08 +02:00
Dmitry Zaytsev 6ad0e214da Updated version name in README 2017-07-04 16:21:58 +02:00
Dmitry Zaytsev 17f897e8a1 Frame processors were called from the main thread 2017-07-04 16:21:32 +02:00
Dmitry Zaytsev 582174aeb1 Updated version number in README 2017-07-03 20:17:47 +02:00
Dmitry Zaytsev 023743135e Added logging to capture renderer parameters 2017-07-03 20:07:06 +02:00
Dmitry Zaytsev ba031c1437 Added more logging to Camera1 2017-07-03 19:46:48 +02:00
Dionysis Lorentzos a951882fe9 fix typo 2017-06-29 20:01:43 +02:00
Dionysis Lorentzos 2d0cc63637 Merge pull request #45 from Fotoapparat/icon
Update lib & sample icon.
2017-06-26 14:10:12 +02:00
Dionysis 5b953c1df2 Update lib & sample icon. 2017-06-25 20:53:13 +02:00
Dionysis f25ea5d39f Revert "Update README.md"
This reverts commit aab760bab8.
2017-06-16 23:51:00 +02:00
Dionysis Lorentzos aab760bab8 Update README.md 2017-06-16 23:48:24 +02:00
Yaohan Chen 330dc6e9e5 Handle null returned from Camera.Parameters#flatten 2017-06-07 16:54:34 -04:00
Yaohan Chen 07c0322432 Make tests compile with changes to SelectorFunction etc 2017-06-07 16:48:01 -04:00
Yaohan Chen e4b01725e4 Merge remote-tracking branch 'upstream/master' 2017-05-26 15:28:27 -04:00
Yaohan Chen 3b8195bbe7 Implement sensor sensitivity setting
Add API for setting sensor sensitivity (FotoapparatBuilder#sensorSensitivity)
Handle conversion to CaptureRequest and Camera.Parameter
WIP #21
2017-05-26 15:05:19 -04:00
Yaohan Chen 1336714343 Move/rename SensorSensitivityCapability to Range
Following advice in https://github.com/Fotoapparat/Fotoapparat/issues/21#issuecomment-304135681

WIP #21
2017-05-26 14:04:17 -04:00
Yaohan Chen c0bd25ce58 Add manual sensor sensitivity (ISO) support (WIP)
Add querying and selection functions
WIP for #21
2017-05-25 16:32:10 -04:00
Yaohan Chen df1079c233 Make SelectorFunction more generic
It has a Input and Output type, so that it's possible to have
SelectorFunctions based on input other than Collection.

Following advice in #21 https://github.com/Fotoapparat/Fotoapparat/issues/21#issuecomment-303858275
2017-05-25 16:19:39 -04:00
377 changed files with 8708 additions and 13285 deletions
+35
View File
@@ -0,0 +1,35 @@
Please check if your issue exists.
Issues should only be posted after you have been able to reproduce them and confirm that they are either a missing functionality or a bug.
#### What are you trying to achieve or the steps to reproduce?
Describe your issue here, include as much detail as neccessary to reproduce the issue
or implement a missing functionality.
```java
// Wrap code in markdown source tags
```
#### How did you initialize FA?
```java
// Wrap code in markdown source tags
```
#### What was the result you received?
#### What did you expect?
#### Context:
* *FA version*:
* *Devices/APIs affected*:
* *any other relevant information*:
+6 -2
View File
@@ -5,9 +5,13 @@ android:
components:
- tools
- platform-tools
- build-tools-25.0.2
- android-25
- tools
- build-tools-27.0.3
- android-27
- extra-android-m2repository
before_install:
- yes | sdkmanager "platforms;android-27"
script:
- ./gradlew build test
+104 -62
View File
@@ -2,27 +2,33 @@
![Build status](https://travis-ci.org/Fotoapparat/Fotoapparat.svg?branch=master)
![ ](sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
Camera API in Android is hard. Having 2 different API for new and old Camera does not make things any easier. But fret not, that is your lucky day! After several years of working with Camera we came up with Fotoapparat.
What it provides:
- Simple, yet powerful API for working with Camera.
- Support of Camera1 as well as Camera2.
- Camera API which does not allow you to shoot yourself in the foot.
- Simple yet powerful parameters customization.
- Standalone custom `CameraView` which can be integrated into any `Activity`.
- Fixes and workarounds for device-specific problems.
- Both Kotlin and Java friendly configurations.
- Last, but not least, non 0% test coverage.
Taking picture becomes as simple as:
```java
Fotoapparat fotoapparat = Fotoapparat
.with(context)
.into(cameraView)
.build();
fotoapparat.start();
```kotlin
val fotoapparat = Fotoapparat(
context = this,
view = cameraView
)
fotoapparat.start()
fotoapparat
.takePicture()
.saveToFile(someFile);
.saveToFile(someFile)
```
## How it works
@@ -40,47 +46,40 @@ Add `CameraView` to your layout
### Step Two
Configure `Fotoapparat` instance
Configure `Fotoapparat` instance.
```kotlin
Fotoapparat(
context = this,
view = cameraView, // view which will draw the camera preview
scaleType = ScaleType.CenterCrop, // (optional) we want the preview to fill the view
lensPosition = back(), // (optional) we want back camera
cameraConfiguration = configuration, // (optional) define an advanced configuration
logger = loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
),
cameraErrorCallback = { error -> } // (optional) log fatal errors
)
```
Check the [wiki for the `configuration` options e.g. change iso](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Kotlin)
Are you using Java only? See our [wiki for the java-friendly configuration](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Java).
```java
Fotoapparat
.with(context)
.into(cameraView) // view which will draw the camera preview
.photoSize(biggestSize()) // we want to have the biggest photo possible
.lensPosition(back()) // we want back camera
.focusMode(firstAvailable( // (optional) use the first focus mode which is supported by device
continuousFocus(),
autoFocus(), // in case if continuous focus is not available on device, auto focus will be used
fixed() // if even auto focus is not available - fixed focus mode will be used
))
.flash(firstAvailable( // (optional) similar to how it is done for focus mode, this time for flash
autoRedEye(),
autoFlash(),
torch()
))
.frameProcessor(myFrameProcessor) // (optional) receives each frame from preview stream
.logger(loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
))
.build();
```
### Step Three
Call `start()` and `stop()`. No rocket science here.
```java
@Override
protected void onStart() {
super.onStart();
fotoapparat.start();
```kotlin
override fun onStart() {
super.onStart()
fotoapparat.start()
}
@Override
protected void onStop() {
super.onStop();
fotoapparat.stop();
override fun onStop() {
super.onStop()
fotoapparat.stop()
}
```
@@ -88,35 +87,74 @@ protected void onStop() {
Finally we are ready to take picture. You have various options.
```java
PhotoResult photoResult = fotoapparat.takePicture();
```kotlin
val photoResult = fotoapparat.takePicture()
// Asynchronously saves photo to file
photoResult.saveToFile(someFile);
photoResult.saveToFile(someFile)
// Asynchronously converts photo to bitmap and returns result on main thread
photoResult
.toBitmap()
.whenAvailable(new PendingResult.Callback<BitmapPhoto>() {
@Override
public void onResult(BitmapPhoto result) {
ImageView imageView = (ImageView) findViewById(R.id.result);
imageView.setImageBitmap(result.bitmap);
imageView.setRotation(-result.rotationDegrees);
}
});
.whenAvailable { bitmapPhoto ->
val imageView = (ImageView) findViewById(R.id.result)
imageView.setImageBitmap(bitmapPhoto.bitmap)
imageView.setRotation(-bitmapPhoto.rotationDegrees)
}
// Of course you can also get a photo in a blocking way. Do not do it on main thread though.
BitmapPhoto result = photoResult.toBitmap().await();
val result = photoResult.toBitmap().await()
// Convert asynchronous events to RxJava 1.x/2.x types. See /fotoapparat-adapters/ module
// Convert asynchronous events to RxJava 1.x/2.x types.
// See /fotoapparat-adapters/ module
photoResult
.toBitmap()
.adapt(SingleAdapter.<BitmapPhoto>toSingle())
.subscribe(bitmapPhoto -> {
.toSingle()
.subscribe { bitmapPhoto ->
});
}
```
## Update parameters
It is also possible to update some parameters after `Fotoapparat` was already started.
```kotlin
fotoapparat.updateConfiguration(
UpdateConfiguration(
flashMode = if (isChecked) torch() else off()
// ...
// all the parameters available in CameraConfiguration
)
)
```
Or alternatively you may provide updates on an existing full configuration.
```kotlin
val configuration = CameraConfiguration(
// A full configuration
// ...
)
fotoapparat.updateConfiguration(
configuration.copy(
flashMode = if (isChecked) torch() else off()
// all the parameters available in CameraConfiguration
)
)
```
## Switch cameras
In order to switch between cameras, `Fotoapparat.switchTo()` can be used with the new desired `lensPosition` and its `cameraConfiguration`.
```kotlin
fotoapparat.switchTo(
lensPosition = front(),
cameraConfiguration = newConfigurationForFrontCamera
)
```
## Set up
@@ -128,16 +166,20 @@ repositories {
maven { url 'https://jitpack.io' }
}
compile 'io.fotoapparat.fotoapparat:library:1.0.2'
compile 'io.fotoapparat.fotoapparat:library:2.0.1'
```
Camera permission will be automatically added to your `AndroidManifest.xml`. Do not forget to request this permission on Marshmallow and higher.
## Face detection
Optionally, you can check out our other library which adds face detection capabilities - [FaceDetector](https://github.com/Fotoapparat/FaceDetector).
## Credits
We want to say thanks to [Mark Murphy](https://github.com/commonsguy) for the awesome job he did with [CWAC-Camera](https://github.com/commonsguy/cwac-camera). We were using his library for a couple of years and now we feel that Fotoapparat is a next step in the right direction.
We also want to say many thanks to [Leander Lenzing](http://leanderlenzing.com/) for the amazing icon. Don't forget to follow his work in [dribbble](https://dribbble.com/leanderlenzing).
## License
+32 -2
View File
@@ -1,17 +1,47 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
versions = [
gradle : '4.4.1',
kotlin : '1.2.10',
code : 1,
name : '1.0.0',
sdk : [
minimum: 16,
target : 27
],
android: [
buildTools: '27.0.3',
support : '27.0.2'
],
rx : [
rxJava1: '1.2.9',
rxJava2: '2.0.8'
],
test : [
junit : '4.12',
mockito: '2.13.0'
],
util : [
commons: '2.5'
]
]
}
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:${project.androidBuildToolsVersion}"
classpath "com.android.tools.build:gradle:3.1.0-alpha06"
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
@@ -21,5 +51,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = project.gradleVersion
gradleVersion = versions.gradle
}
+21
View File
@@ -5,6 +5,18 @@ The child modules contained herein are additional adapters for other popular exe
To use, supply an instance of your desired adapter when performing a task of a PendingResult.
Kotlin:
```java
fotoapparat.takePicture()
.toBitmap()
.toObservable()
.subscribe { bitmapPhoto ->
// Do something with the photo
}
```
Java:
```java
fotoapparat.takePicture()
.toBitmap()
@@ -16,3 +28,12 @@ fotoapparat.takePicture()
}
});
```
Supported types:
* `Observable<T>` : RxJava 1/2
* `Flowable<T>` : RxJava 2
* `Single<T>` : RxJava 1/2
* `Completable` : RxJava 1/2
+11 -7
View File
@@ -1,23 +1,27 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'adapter-rxjava'
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex:rxjava:${project.rxjava1Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex:rxjava:${versions.rx.rxJava1}"
testCompile "junit:junit:${project.junitVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex:rxjava:${versions.rx.rxJava1}"
testImplementation "junit:junit:${versions.test.junit}"
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
fun <T> toCompletable(): Function1<Future<T>, Completable> {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.from(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.from(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.from(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.from(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
+1
View File
@@ -5,6 +5,7 @@ An `Adapter` for adapting RxJava 2.x types.
Available types:
* `Observable<T>`
* `Flowable<T>`
* `Single<T>`
* `Completable`
+11 -7
View File
@@ -1,23 +1,27 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'adapter-rxjava2'
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex.rxjava2:rxjava:${project.rxjava2Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
testCompile "junit:junit:${project.junitVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
testImplementation "junit:junit:${versions.test.junit}"
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,31 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <R> toCompletable(): (Future<R>) -> Completable {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Flowable
import java.util.concurrent.Future
/**
* Adapter for [Flowable].
*/
object FlowableAdapter {
/**
* @return Adapter which adapts result to [Flowable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toFlowable(): Function1<Future<T>, Flowable<T>> {
return { future -> Flowable.fromFuture(future) }
}
}
/**
* @return A [Flowable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toFlowable(): Flowable<T> {
return adapt { future -> Flowable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.fromFuture(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.fromFuture(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.fromFuture(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava2
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class CompletableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -0,0 +1,55 @@
package io.fotoapparat.result.adapter.rxjava2;
import org.junit.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import io.reactivex.subscribers.TestSubscriber;
public class FlowableAdapterTest {
private TestSubscriber<String> observer = new TestSubscriber<>();
@Test
public void completed() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello";
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertValue("Hello");
observer.assertNoErrors();
}
@Test
public void error() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("What a failure");
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertNoValues();
observer.assertError(ExecutionException.class);
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class ObservableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class SingleAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
+18 -9
View File
@@ -1,15 +1,18 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
kotlin.experimental.coroutines 'enable'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'library'
}
@@ -19,13 +22,19 @@ android {
minifyEnabled false
}
}
testOptions {
unitTests.returnDefaultValues = true
}
}
dependencies {
compile "com.android.support:appcompat-v7:${project.appCompatVersion}"
testCompile "junit:junit:${project.junitVersion}"
testCompile "org.mockito:mockito-core:${project.mockitoVersion}"
testCompile 'commons-io:commons-io:2.5'
compile "com.android.support:support-annotations:${versions.android.support}"
compile "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21'
testImplementation "junit:junit:${versions.test.junit}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
testImplementation "org.mockito:mockito-core:${versions.test.mockito}"
testImplementation "commons-io:commons-io:${versions.util.commons}"
}
@@ -1,243 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.orientation.OrientationSensor;
import io.fotoapparat.hardware.orientation.RotationListener;
import io.fotoapparat.hardware.orientation.ScreenOrientationProvider;
import io.fotoapparat.parameter.provider.CapabilitiesProvider;
import io.fotoapparat.parameter.provider.InitialParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersValidator;
import io.fotoapparat.result.CapabilitiesResult;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.routine.AutoFocusRoutine;
import io.fotoapparat.routine.CheckAvailabilityRoutine;
import io.fotoapparat.routine.ConfigurePreviewStreamRoutine;
import io.fotoapparat.routine.StartCameraRoutine;
import io.fotoapparat.routine.StopCameraRoutine;
import io.fotoapparat.routine.TakePictureRoutine;
import io.fotoapparat.routine.UpdateOrientationRoutine;
/**
* Camera. Takes pictures.
*/
public class Fotoapparat {
private static final Executor SERIAL_EXECUTOR = Executors.newSingleThreadExecutor();
private final StartCameraRoutine startCameraRoutine;
private final StopCameraRoutine stopCameraRoutine;
private final UpdateOrientationRoutine updateOrientationRoutine;
private final ConfigurePreviewStreamRoutine configurePreviewStreamRoutine;
private final CapabilitiesProvider capabilitiesProvider;
private final TakePictureRoutine takePictureRoutine;
private final AutoFocusRoutine autoFocusRoutine;
private final CheckAvailabilityRoutine checkAvailabilityRoutine;
private final Executor executor;
private boolean started = false;
Fotoapparat(StartCameraRoutine startCameraRoutine,
StopCameraRoutine stopCameraRoutine,
UpdateOrientationRoutine updateOrientationRoutine,
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine,
CapabilitiesProvider capabilitiesProvider,
TakePictureRoutine takePictureRoutine,
AutoFocusRoutine autoFocusRoutine,
CheckAvailabilityRoutine checkAvailabilityRoutine,
Executor executor) {
this.startCameraRoutine = startCameraRoutine;
this.stopCameraRoutine = stopCameraRoutine;
this.updateOrientationRoutine = updateOrientationRoutine;
this.configurePreviewStreamRoutine = configurePreviewStreamRoutine;
this.capabilitiesProvider = capabilitiesProvider;
this.takePictureRoutine = takePictureRoutine;
this.autoFocusRoutine = autoFocusRoutine;
this.checkAvailabilityRoutine = checkAvailabilityRoutine;
this.executor = executor;
}
public static FotoapparatBuilder with(Context context) {
if (context == null) {
throw new IllegalStateException("Context is null.");
}
return new FotoapparatBuilder(context);
}
static Fotoapparat create(FotoapparatBuilder builder) {
CameraDevice cameraDevice = builder.cameraProvider.get(builder.logger);
ScreenOrientationProvider screenOrientationProvider = new ScreenOrientationProvider(builder.context);
RotationListener rotationListener = new RotationListener(builder.context);
InitialParametersValidator parametersValidator = new InitialParametersValidator();
InitialParametersProvider initialParametersProvider = new InitialParametersProvider(
cameraDevice,
builder.photoSizeSelector,
builder.previewSizeSelector,
builder.focusModeSelector,
builder.flashSelector,
parametersValidator
);
StartCameraRoutine startCameraRoutine = new StartCameraRoutine(
cameraDevice,
builder.renderer,
builder.lensPositionSelector,
screenOrientationProvider,
initialParametersProvider
);
StopCameraRoutine stopCameraRoutine = new StopCameraRoutine(cameraDevice);
OrientationSensor orientationSensor = new OrientationSensor(
rotationListener,
screenOrientationProvider
);
UpdateOrientationRoutine updateOrientationRoutine = new UpdateOrientationRoutine(
cameraDevice,
orientationSensor,
SERIAL_EXECUTOR
);
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine = new ConfigurePreviewStreamRoutine(
cameraDevice,
builder.frameProcessor
);
CapabilitiesProvider capabilitiesProvider = new CapabilitiesProvider(
cameraDevice,
SERIAL_EXECUTOR
);
TakePictureRoutine takePictureRoutine = new TakePictureRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
AutoFocusRoutine autoFocusRoutine = new AutoFocusRoutine(cameraDevice);
CheckAvailabilityRoutine checkAvailabilityRoutine = new CheckAvailabilityRoutine(
cameraDevice,
builder.lensPositionSelector
);
return new Fotoapparat(
startCameraRoutine,
stopCameraRoutine,
updateOrientationRoutine,
configurePreviewStreamRoutine,
capabilitiesProvider,
takePictureRoutine,
autoFocusRoutine,
checkAvailabilityRoutine,
SERIAL_EXECUTOR
);
}
/**
* @return {@code true} if camera for this {@link Fotoapparat} is available. {@code false} if
* it is not available.
*/
public boolean isAvailable() {
return checkAvailabilityRoutine.isAvailable();
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return {@link CapabilitiesResult} which will deliver result asynchronously.
*/
public CapabilitiesResult getCapabilities() {
ensureStarted();
return capabilitiesProvider.getCapabilities();
}
/**
* Takes picture. Returns immediately.
*
* @return {@link PhotoResult} which will deliver result asynchronously.
*/
public PhotoResult takePicture() {
ensureStarted();
return takePictureRoutine.takePicture();
}
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*/
public Fotoapparat autoFocus() {
ensureStarted();
executor.execute(
autoFocusRoutine
);
return this;
}
/**
* Starts camera.
*
* @throws IllegalStateException if camera was already started.
*/
public void start() {
ensureNotStarted();
started = true;
startCamera();
configurePreviewStream();
updateOrientationRoutine.start();
}
/**
* Stops camera.
*
* @throws IllegalStateException if camera is not started.
*/
public void stop() {
ensureStarted();
started = false;
updateOrientationRoutine.stop();
stopCamera();
}
private void startCamera() {
executor.execute(
startCameraRoutine
);
}
private void stopCamera() {
executor.execute(
stopCameraRoutine
);
}
private void configurePreviewStream() {
executor.execute(
configurePreviewStreamRoutine
);
}
private void ensureStarted() {
if (!started) {
throw new IllegalStateException("Camera is not started!");
}
}
private void ensureNotStarted() {
if (started) {
throw new IllegalStateException("Camera is already started!");
}
}
}
@@ -0,0 +1,247 @@
package io.fotoapparat
import android.content.Context
import android.support.annotation.FloatRange
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.error.onMainThread
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.hardware.execute
import io.fotoapparat.hardware.executeTask
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.result.*
import io.fotoapparat.routine.camera.bootStart
import io.fotoapparat.routine.camera.shutDown
import io.fotoapparat.routine.camera.switchCamera
import io.fotoapparat.routine.camera.updateDeviceConfiguration
import io.fotoapparat.routine.capability.getCapabilities
import io.fotoapparat.routine.focus.focus
import io.fotoapparat.routine.parameter.getCurrentParameters
import io.fotoapparat.routine.photo.takePhoto
import io.fotoapparat.routine.zoom.updateZoomLevel
import io.fotoapparat.selector.back
import io.fotoapparat.selector.external
import io.fotoapparat.selector.firstAvailable
import io.fotoapparat.selector.front
import io.fotoapparat.view.CameraRenderer
import java.util.concurrent.FutureTask
/**
* Camera. Takes pictures.
*/
class Fotoapparat
@JvmOverloads constructor(
context: Context,
view: CameraRenderer,
lensPosition: Collection<LensPosition>.() -> LensPosition? = firstAvailable(
back(),
front(),
external()
),
scaleType: ScaleType = ScaleType.CenterCrop,
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
cameraErrorCallback: ((CameraException) -> Unit) = {},
private val logger: Logger = none()
) {
private val mainThreadErrorCallback = cameraErrorCallback.onMainThread()
private val display = Display(context)
private val device = Device(
cameraRenderer = view,
logger = logger,
display = display,
scaleType = scaleType,
initialLensPositionSelector = lensPosition,
initialConfiguration = cameraConfiguration
)
private val orientationSensor = OrientationSensor(
context = context,
device = device
)
init {
logger.recordMethod()
}
/**
* Starts camera.
*
* @throws IllegalStateException If the camera has already started.
*/
fun start() {
logger.recordMethod()
execute {
device.bootStart(
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
}
}
/**
* Stops camera.
*
* @throws IllegalStateException If the camera has not started.
*/
fun stop() {
logger.recordMethod()
execute {
device.shutDown(
orientationSensor = orientationSensor
)
}
}
/**
* Takes picture, returns immediately.
*
* @return [PhotoResult] which will deliver result asynchronously.
*/
fun takePicture(): PhotoResult {
logger.recordMethod()
val takePictureTask = FutureTask<Photo> {
device.takePhoto()
}
executeTask(takePictureTask)
return PhotoResult.fromFuture(takePictureTask, logger)
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return [CapabilitiesResult] which will deliver result asynchronously.
*/
fun getCapabilities(): CapabilitiesResult {
logger.recordMethod()
val getCapabilitiesTask = FutureTask<Capabilities> {
device.getCapabilities()
}
executeTask(getCapabilitiesTask)
return PendingResult.fromFuture(getCapabilitiesTask, logger)
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return [ParametersResult] which will deliver result asynchronously.
*/
fun getCurrentParameters(): ParametersResult {
logger.recordMethod()
val getCameraParametersTask = FutureTask<CameraParameters> {
device.getCurrentParameters()
}
executeTask(getCameraParametersTask)
return PendingResult.fromFuture(getCameraParametersTask, logger)
}
/**
* Updates current configuration.
*
* @throws IllegalStateException If the current camera has not started.
*/
fun updateConfiguration(newConfiguration: Configuration) = execute {
logger.recordMethod()
device.updateDeviceConfiguration(newConfiguration)
}
/**
* Asynchronously updates zoom level of the camera.
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel Zoom level of the camera. A value between 0 and 1.
* @throws IllegalStateException If the current camera has not started.
*/
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = execute {
logger.recordMethod()
device.updateZoomLevel(
zoomLevel = zoomLevel
)
}
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*
* @see Fotoapparat.focus
*/
fun autoFocus(): Fotoapparat {
logger.recordMethod()
focus()
return this
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*
* @see Fotoapparat.autoFocus
*/
fun focus(): PendingResult<FocusResult> {
logger.recordMethod()
val focusTask = FutureTask<FocusResult> {
device.focus()
}
executeTask(focusTask)
return PendingResult.fromFuture(focusTask, logger)
}
/**
* Switches to another camera. If previous camera has already started then it will be
* stopped automatically and new will start.
*/
fun switchTo(
lensPosition: Collection<LensPosition>.() -> LensPosition?,
cameraConfiguration: CameraConfiguration
) {
logger.recordMethod()
execute {
device.switchCamera(
newLensPositionSelector = lensPosition,
newConfiguration = cameraConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
)
}
}
/**
* @return `true` if selected lens position is available. `false` if it is not available.
*/
fun isAvailable(
selector: (Collection<LensPosition>) -> LensPosition?
): Boolean {
return device.canSelectCamera(selector)
}
companion object {
@JvmStatic
fun with(context: Context) = FotoapparatBuilder(context)
}
}
@@ -1,164 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.NonNull;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.provider.CameraProvider;
import io.fotoapparat.log.Logger;
import io.fotoapparat.log.Loggers;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.selector.FlashSelectors;
import io.fotoapparat.parameter.selector.SelectorFunction;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.view.CameraRenderer;
import io.fotoapparat.view.CameraView;
import static io.fotoapparat.hardware.provider.CameraProviders.v1;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.autoFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.continuousFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.fixed;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.back;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.external;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.front;
import static io.fotoapparat.parameter.selector.Selectors.firstAvailable;
import static io.fotoapparat.parameter.selector.SizeSelectors.biggestSize;
/**
* Builder for {@link Fotoapparat}.
*/
public class FotoapparatBuilder {
Context context;
CameraProvider cameraProvider = v1();
CameraRenderer renderer;
SelectorFunction<LensPosition> lensPositionSelector = firstAvailable(
back(),
front(),
external()
);
SelectorFunction<Size> photoSizeSelector = biggestSize();
SelectorFunction<Size> previewSizeSelector = biggestSize();
SelectorFunction<FocusMode> focusModeSelector = firstAvailable(
continuousFocus(),
autoFocus(),
fixed()
);
SelectorFunction<Flash> flashSelector = FlashSelectors.off();
FrameProcessor frameProcessor = null;
Logger logger = Loggers.none();
FotoapparatBuilder(@NonNull Context context) {
this.context = context;
}
/**
* @param cameraProvider decides which {@link CameraDevice} to use.
*/
public FotoapparatBuilder cameraProvider(CameraProvider cameraProvider) {
this.cameraProvider = cameraProvider;
return this;
}
/**
* @param selector selects size of the photo (in pixels) from list of available sizes.
*/
public FotoapparatBuilder photoSize(SelectorFunction<Size> selector) {
photoSizeSelector = selector;
return this;
}
/**
* @param selector selects size of preview stream (in pixels) from list of available sizes.
*/
public FotoapparatBuilder previewSize(SelectorFunction<Size> selector) {
previewSizeSelector = selector;
return this;
}
/**
* @param selector selects focus mode from list of available modes.
*/
public FotoapparatBuilder focusMode(SelectorFunction<FocusMode> selector) {
focusModeSelector = selector;
return this;
}
/**
* @param selector selects flash mode from list of available modes.
*/
public FotoapparatBuilder flash(SelectorFunction<Flash> selector) {
flashSelector = selector;
return this;
}
/**
* @param selector camera sensor position from list of available positions.
*/
public FotoapparatBuilder lensPosition(SelectorFunction<LensPosition> selector) {
lensPositionSelector = selector;
return this;
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
public FotoapparatBuilder frameProcessor(FrameProcessor frameProcessor) {
this.frameProcessor = frameProcessor;
return this;
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see Loggers
*/
public FotoapparatBuilder logger(Logger logger) {
this.logger = logger;
return this;
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
public FotoapparatBuilder into(CameraRenderer renderer) {
this.renderer = renderer;
return this;
}
/**
* @return set up instance of {@link Fotoapparat}.
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
public Fotoapparat build() {
validate();
return Fotoapparat.create(this);
}
private void validate() {
if (cameraProvider == null) {
throw new IllegalStateException("CameraProvider is mandatory.");
}
if (renderer == null) {
throw new IllegalStateException("CameraRenderer is mandatory.");
}
if (lensPositionSelector == null) {
throw new IllegalStateException("LensPosition selector is mandatory.");
}
if (photoSizeSelector == null) {
throw new IllegalStateException("Photo size selector is mandatory.");
}
}
}
@@ -0,0 +1,186 @@
package io.fotoapparat
import android.content.Context
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
import io.fotoapparat.selector.back
import io.fotoapparat.selector.external
import io.fotoapparat.selector.firstAvailable
import io.fotoapparat.selector.front
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.CameraView
/**
* Builder for [Fotoapparat].
*/
class FotoapparatBuilder internal constructor(private var context: Context) {
internal var lensPositionSelector: Iterable<LensPosition>.() -> LensPosition? = firstAvailable(
back(),
front(),
external()
)
internal var cameraErrorCallback: (CameraException) -> Unit = {}
internal var renderer: CameraRenderer? = null
internal var scaleType: ScaleType = ScaleType.CenterCrop
internal var logger: Logger = none()
internal var configuration = CameraConfiguration.default()
/**
* @param selector camera sensor position from list of available positions.
*/
fun lensPosition(selector: Iterable<LensPosition>.() -> LensPosition?): FotoapparatBuilder {
lensPositionSelector = selector
return this
}
/**
* @param scaleType of preview inside the view.
*/
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder {
this.scaleType = scaleType
return this
}
/**
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
*/
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
configuration = configuration.copy(
pictureResolution = selector
)
return this
}
/**
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
*/
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
configuration = configuration.copy(
previewResolution = selector
)
return this
}
/**
* @param selector selects focus mode from list of available modes.
*/
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): FotoapparatBuilder {
configuration = configuration.copy(
focusMode = selector
)
return this
}
/**
* @param selector selects flash mode from list of available modes.
*/
fun flash(selector: Iterable<Flash>.() -> Flash?): FotoapparatBuilder {
configuration = configuration.copy(
flashMode = selector
)
return this
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): FotoapparatBuilder {
configuration = configuration.copy(
previewFpsRange = selector
)
return this
}
/**
* @param selector selects ISO value from range of available values.
*/
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): FotoapparatBuilder {
configuration = configuration.copy(
sensorSensitivity = selector
)
return this
}
/**
* @param selector of the Jpeg picture quality.
*/
fun jpegQuality(selector: IntRange.() -> Int?): FotoapparatBuilder {
configuration = configuration.copy(
jpegQuality = selector
)
return this
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
fun frameProcessor(frameProcessor: (Frame) -> Unit): FotoapparatBuilder {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
return this
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see io.fotoapparat.log.Loggers
*/
fun logger(logger: Logger): FotoapparatBuilder {
this.logger = logger
return this
}
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorCallback
*/
fun cameraErrorCallback(callback: (CameraException) -> Unit): FotoapparatBuilder {
cameraErrorCallback = callback
return this
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
fun into(renderer: CameraRenderer): FotoapparatBuilder {
this.renderer = renderer
return this
}
/**
* @return set up instance of [Fotoapparat].
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
fun build() = buildInternal(
renderer = renderer
)
private fun buildInternal(
renderer: CameraRenderer?
): Fotoapparat {
if (renderer == null) {
throw IllegalStateException("CameraRenderer is mandatory.")
}
return Fotoapparat(
context = context,
view = renderer,
lensPosition = lensPositionSelector,
cameraConfiguration = configuration,
scaleType = scaleType,
cameraErrorCallback = cameraErrorCallback,
logger = logger
)
}
}
@@ -1,77 +0,0 @@
package io.fotoapparat;
import android.support.annotation.NonNull;
/**
* Switches between different instances of {@link Fotoapparat}. Convenient when you want to allow
* user to switch between different cameras or configurations.
* <p>
* This class is not thread safe. Consider using it from a single thread.
*/
public class FotoapparatSwitcher {
@NonNull
private Fotoapparat fotoapparat;
private boolean started = false;
private FotoapparatSwitcher(@NonNull Fotoapparat fotoapparat) {
this.fotoapparat = fotoapparat;
}
/**
* @return {@link FotoapparatSwitcher} with given {@link Fotoapparat} used by default.
*/
public static FotoapparatSwitcher withDefault(@NonNull Fotoapparat fotoapparat) {
return new FotoapparatSwitcher(fotoapparat);
}
/**
* Starts {@link Fotoapparat} associated with this switcher. Every new {@link Fotoapparat} will
* be started automatically until {@link #stop()} is called.
*
* @throws IllegalStateException if switcher is already started.
*/
public void start() {
fotoapparat.start();
started = true;
}
/**
* Stops currently used {@link Fotoapparat}.
*
* @throws IllegalStateException if switcher is already stopped.
*/
public void stop() {
fotoapparat.stop();
started = false;
}
/**
* Switches to another {@link Fotoapparat}. If switcher is already started then previously used
* {@link Fotoapparat} will be stopped automatically and new {@link Fotoapparat} will be
* started.
*
* @param fotoapparat new {@link Fotoapparat} to use.
* @throws NullPointerException if given {@link Fotoapparat} is {@code null}.
*/
public void switchTo(@NonNull Fotoapparat fotoapparat) {
if (started) {
this.fotoapparat.stop();
fotoapparat.start();
}
this.fotoapparat = fotoapparat;
}
/**
* @return currently used instance of {@link Fotoapparat}.
*/
@NonNull
public Fotoapparat getCurrentFotoapparat() {
return fotoapparat;
}
}
@@ -0,0 +1,53 @@
package io.fotoapparat.capability
import io.fotoapparat.parameter.*
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.util.wrap
/**
* Capabilities of camera hardware.
*
* Sensor sensitivities is not guaranteed to always contain a value.
*/
data class Capabilities(
val canZoom: Boolean,
val flashModes: Set<Flash>,
val focusModes: Set<FocusMode>,
val canSmoothZoom: Boolean,
val jpegQualityRange: IntRange,
val previewFpsRanges: Set<FpsRange>,
val antiBandingModes: Set<AntiBandingMode>,
val pictureResolutions: Set<Resolution>,
val previewResolutions: Set<Resolution>,
val sensorSensitivities: Set<Int>
) {
init {
flashModes.ensureNotEmpty()
focusModes.ensureNotEmpty()
antiBandingModes.ensureNotEmpty()
previewFpsRanges.ensureNotEmpty()
pictureResolutions.ensureNotEmpty()
previewResolutions.ensureNotEmpty()
}
override fun toString(): String {
return "Capabilities" + lineSeparator +
"canZoom:" + canZoom.wrap() +
"flashModes:" + flashModes.wrap() +
"focusModes:" + focusModes.wrap() +
"canSmoothZoom:" + canSmoothZoom.wrap() +
"jpegQualityRange:" + jpegQualityRange.wrap() +
"antiBandingModes:" + antiBandingModes.wrap() +
"previewFpsRanges:" + previewFpsRanges.wrap() +
"pictureResolutions:" + pictureResolutions.wrap() +
"previewResolutions:" + previewResolutions.wrap() +
"sensorSensitivities:" + sensorSensitivities.wrap()
}
}
private inline fun <reified E> Set<E>.ensureNotEmpty() {
if (isEmpty()) {
throw IllegalArgumentException("Capabilities cannot have an empty Set<${E::class.java.simpleName}>.")
}
}
@@ -0,0 +1,32 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.capability.provide
import android.hardware.Camera
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.parameter.SupportedParameters
import io.fotoapparat.parameter.camera.convert.*
/**
* Returns the [io.fotoapparat.capability.Capabilities] of the given [Camera].
*/
internal fun Camera.getCapabilities() = SupportedParameters(parameters).getCapabilities()
private fun SupportedParameters.getCapabilities(): Capabilities {
return Capabilities(
canZoom = supportedZoom,
flashModes = flashModes.extract { it.toFlash() },
focusModes = focusModes.extract { it.toFocusMode() },
canSmoothZoom = supportedSmoothZoom,
jpegQualityRange = jpegQualityRange,
antiBandingModes = supportedAutoBandingModes.extract { it.toAntiBandingMode() },
sensorSensitivities = sensorSensitivities.toSet(),
previewFpsRanges = supportedPreviewFpsRanges.extract { it.toFpsRange() },
pictureResolutions = pictureResolutions.mapSizes(),
previewResolutions = previewResolutions.mapSizes()
)
}
private fun <Parameter : Any, Code> List<Code>.extract(converter: (Code) -> Parameter?) = mapNotNull { converter(it) }.toSet()
private fun Collection<Camera.Size>.mapSizes() = map { it.toResolution() }.toSet()
@@ -0,0 +1,18 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
/**
* Returns the [Characteristics] for the given `cameraId`.
*/
internal fun getCharacteristics(cameraId: Int): Characteristics {
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
return Characteristics(
cameraId,
info.facing.toLensPosition(),
info.orientation
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.characteristic
/**
* A camera characteristic.
*/
interface Characteristic
@@ -0,0 +1,10 @@
package io.fotoapparat.characteristic
/**
* A set of information about the camera.
*/
internal data class Characteristics(
val cameraId: Int,
val lensPosition: LensPosition,
val orientation: Int
)
@@ -1,23 +1,23 @@
package io.fotoapparat.parameter;
package io.fotoapparat.characteristic
/**
* The camera position relatively to the screen of the device.
*/
public enum LensPosition {
sealed class LensPosition : Characteristic {
/**
* The back camera.
*/
BACK,
object Back : LensPosition()
/**
* The front camera.
*/
FRONT,
object Front : LensPosition()
/**
* An external camera.
*/
EXTERNAL
object External : LensPosition()
}
}
@@ -0,0 +1,35 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
import io.fotoapparat.exception.camera.CameraException
/**
* Maps between [LensPosition] and Camera v1 lens position code id.
*
* @receiver Camera facing info id.
* @return [LensPosition] from the given lens position code id.
* `null` if position code id is not supported.
*/
internal fun Int.toLensPosition(): LensPosition {
return when (this) {
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
else -> throw IllegalArgumentException("Lens position $this is not supported.")
}
}
/**
* Maps between [LensPosition] and Camera v1 code id.
*
* @receiver [LensPosition]
* @return code of the camera as in [Camera.CameraInfo].
*/
fun LensPosition.toCameraId(): Int {
return (0 until Camera.getNumberOfCameras())
.find {
this == getCharacteristics(it).lensPosition
}
?: throw CameraException("Device has no camera for the desired lens position(s).")
}
@@ -0,0 +1,132 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
import io.fotoapparat.preview.FrameProcessor
import io.fotoapparat.selector.*
private const val DEFAULT_JPEG_QUALITY = 90
/**
* A camera configuration which has all it's selectors defined.
*/
data class CameraConfiguration(
override val flashMode: Iterable<Flash>.() -> Flash? = off(),
override val focusMode: Iterable<FocusMode>.() -> FocusMode? = firstAvailable(
continuousFocusPicture(),
autoFocus(),
fixed()
),
override val jpegQuality: (IntRange) -> Int? = manualJpegQuality(DEFAULT_JPEG_QUALITY),
override val frameProcessor: (Frame) -> Unit = {},
override val previewFpsRange: Iterable<FpsRange>.() -> FpsRange? = highestFps(),
override val antiBandingMode: Iterable<AntiBandingMode>.() -> AntiBandingMode? = firstAvailable(
auto(),
hz50(),
hz60(),
none()
),
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
override val pictureResolution: Iterable<Resolution>.() -> Resolution? = highestResolution(),
override val previewResolution: Iterable<Resolution>.() -> Resolution? = highestResolution()
) : Configuration {
/**
* Builder for [CameraConfiguration].
*/
class Builder internal constructor() {
private var cameraConfiguration: CameraConfiguration = default()
fun flash(selector: (Iterable<Flash>.() -> Flash?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
flashMode = selector
)
return this
}
fun focusMode(selector: (Iterable<FocusMode>.() -> FocusMode?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
focusMode = selector
)
return this
}
fun previewFpsRange(selector: (Iterable<FpsRange>.() -> FpsRange?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
previewFpsRange = selector
)
return this
}
fun sensorSensitivity(selector: (Iterable<Int>.() -> Int?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
)
return this
}
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
cameraConfiguration.copy(
antiBandingMode = selector
)
return this
}
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
cameraConfiguration.copy(
jpegQuality = selector
)
return this
}
fun previewResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
previewResolution = selector
)
return this
}
fun photoResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
pictureResolution = selector
)
return this
}
fun frameProcessor(frameProcessor: FrameProcessor): Builder {
cameraConfiguration = cameraConfiguration.copy(
frameProcessor = { frameProcessor.process(it) }
)
return this
}
/**
* Builds a new [CameraConfiguration].
*/
fun build(): CameraConfiguration = cameraConfiguration
}
companion object {
/**
* Alias for [CameraConfiguration.default]
*/
@JvmStatic
fun standard() = default()
/**
* Default [CameraConfiguration].
*/
@JvmStatic
fun default() = CameraConfiguration()
/**
* Creates a new [CameraConfiguration.Builder].
*/
@JvmStatic
fun builder() = CameraConfiguration.Builder()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
interface Configuration {
val flashMode: (Iterable<Flash>.() -> Flash?)?
val focusMode: (Iterable<FocusMode>.() -> FocusMode?)?
val jpegQuality: ((IntRange) -> Int?)?
val frameProcessor: ((Frame) -> Unit)?
val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)?
val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)?
val sensorSensitivity: (Iterable<Int>.() -> Int?)?
val previewResolution: (Iterable<Resolution>.() -> Resolution?)?
val pictureResolution: (Iterable<Resolution>.() -> Resolution?)?
}
@@ -0,0 +1,105 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
/**
* A camera update configuration.
*/
data class UpdateConfiguration(
override val flashMode: (Iterable<Flash>.() -> Flash?)? = null,
override val focusMode: (Iterable<FocusMode>.() -> FocusMode?)? = null,
override val jpegQuality: ((IntRange) -> Int?)? = null,
override val frameProcessor: ((Frame) -> Unit)? = null,
override val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)? = null,
override val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)? = null,
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
override val previewResolution: (Iterable<Resolution>.() -> Resolution?)? = null,
override val pictureResolution: (Iterable<Resolution>.() -> Resolution?)? = null
) : Configuration {
/**
* Builder for [UpdateConfiguration].
*/
class Builder internal constructor() {
private var configuration = UpdateConfiguration()
fun flash(selector: Iterable<Flash>.() -> Flash?): Builder {
configuration.copy(
flashMode = selector
)
return this
}
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): Builder {
configuration.copy(
focusMode = selector
)
return this
}
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): Builder {
configuration.copy(
previewFpsRange = selector
)
return this
}
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): Builder {
configuration.copy(
sensorSensitivity = selector
)
return this
}
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
configuration.copy(
antiBandingMode = selector
)
return this
}
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
configuration.copy(
jpegQuality = selector
)
return this
}
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
configuration.copy(
previewResolution = selector
)
return this
}
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
configuration.copy(
pictureResolution = selector
)
return this
}
fun frameProcessor(frameProcessor: (Frame) -> Unit): Builder {
configuration.copy(
frameProcessor = frameProcessor
)
return this
}
/**
* Builds a new [UpdateConfiguration].
*/
fun build(): UpdateConfiguration = configuration
}
companion object {
/**
* Creates a new [UpdateConfiguration.Builder].
*/
@JvmStatic
fun builder() = UpdateConfiguration.Builder()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.error
import android.os.Looper
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.executeMainThread
/**
* @return CameraErrorCallback which will always move execution to the main thread.
*/
fun ((CameraException) -> Unit).onMainThread(): (CameraException) -> Unit = { cameraException ->
if (Looper.myLooper() == Looper.getMainLooper()) {
this(cameraException)
} else {
executeMainThread { this(cameraException) }
}
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when there is a problem while saving the file.
*/
class FileSaveException(cause: Throwable) : RuntimeException(cause)
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when zoom level is outside of [0..1] range.
*/
class LevelOutOfRangeException(zoomLevel: Float) : RuntimeException(zoomLevel.toString() + " is out of range [0..1]")
@@ -0,0 +1,11 @@
package io.fotoapparat.exception
/**
* Exception which is not caused by developer and can be either ignored or recovered from.
*/
open class RecoverableRuntimeException : RuntimeException {
constructor(message: String) : super(message)
constructor(throwable: Throwable) : super(throwable)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when it is not possible to decode bitmap from byte array.
*/
class UnableToDecodeBitmapException : RecoverableRuntimeException("Unable to decode bitmap")
@@ -0,0 +1,12 @@
package io.fotoapparat.exception.camera
/**
* A generic camera exception.
*/
open class CameraException(
message: String,
cause: Throwable? = null
) : RuntimeException(
message,
cause
)
@@ -0,0 +1,30 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a selected [Parameter] is not in the supported set.
*
* e.g. Camera supports flash `on`, `off` & `auto` and you ask for a `tomato`.
*/
internal class InvalidConfigurationException : CameraException {
constructor(
value: Any,
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters: $supportedParameters"
)
constructor(
value: Any,
klass: Class<out Comparable<*>>,
supportedRange: ClosedRange<*>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,25 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a configuration selector couldn't select a value.
*/
internal class UnsupportedConfigurationException : CameraException {
constructor(
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector couldn't select a value. " +
"Supported parameters: $supportedParameters"
)
constructor(
configurationName: String,
supportedRange: ClosedRange<*>
) : super(
"$configurationName configuration selector couldn't select a value. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception.camera
/**
* Thrown to indicate that the device has no camera for the desired lens position(s).
*/
class UnsupportedLensException : CameraException("Device has no camera for the desired lens position(s).")
@@ -0,0 +1,19 @@
package io.fotoapparat.exif
import io.fotoapparat.result.Photo
import java.io.File
/**
* Writes Exif orientation attributes.
*/
internal interface ExifOrientationWriter {
/**
* Writes EXIF orientation tag into a file, overwriting it if it already exists.
*
* @param file File of the image.
* @param photo Photo stored in the file.
* @throws FileSaveException If writing has failed.
*/
fun writeExifOrientation(file: File, photo: Photo)
}
@@ -0,0 +1,40 @@
package io.fotoapparat.exif
import android.media.ExifInterface
import io.fotoapparat.exception.FileSaveException
import io.fotoapparat.result.Photo
import java.io.File
import java.io.IOException
/**
* Writes Exif attributes.
*/
internal object ExifWriter : ExifOrientationWriter {
@Throws(FileSaveException::class)
override fun writeExifOrientation(file: File, photo: Photo) {
try {
val exifInterface = ExifInterface(file.path)
exifInterface.setAttribute(
ExifInterface.TAG_ORIENTATION,
toExifOrientation(photo.rotationDegrees).toString()
)
exifInterface.saveAttributes()
} catch (e: IOException) {
throw FileSaveException(e)
}
}
private fun toExifOrientation(rotationDegrees: Int): Int {
val compensationRotationDegrees = (360 - rotationDegrees) % 360
return when (compensationRotationDegrees) {
90 -> ExifInterface.ORIENTATION_ROTATE_90
180 -> ExifInterface.ORIENTATION_ROTATE_180
270 -> ExifInterface.ORIENTATION_ROTATE_270
else -> ExifInterface.ORIENTATION_NORMAL
}
}
}
@@ -1,74 +0,0 @@
package io.fotoapparat.hardware;
import java.util.List;
import io.fotoapparat.hardware.operators.AutoFocusOperator;
import io.fotoapparat.hardware.operators.CapabilitiesOperator;
import io.fotoapparat.hardware.operators.CaptureOperator;
import io.fotoapparat.hardware.operators.ConnectionOperator;
import io.fotoapparat.hardware.operators.ExposureMeasurementOperator;
import io.fotoapparat.hardware.operators.OrientationOperator;
import io.fotoapparat.hardware.operators.ParametersOperator;
import io.fotoapparat.hardware.operators.PreviewOperator;
import io.fotoapparat.hardware.operators.PreviewStreamOperator;
import io.fotoapparat.hardware.operators.RendererParametersOperator;
import io.fotoapparat.hardware.operators.SurfaceOperator;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
import io.fotoapparat.lens.FocusResult;
/**
* Abstraction for camera hardware.
*/
public interface CameraDevice extends CaptureOperator,
PreviewOperator, CapabilitiesOperator, OrientationOperator, ParametersOperator,
ConnectionOperator, SurfaceOperator, PreviewStreamOperator, RendererParametersOperator,
ExposureMeasurementOperator, AutoFocusOperator, AvailableLensPositionsProvider {
@Override
void open(LensPosition lensPosition);
@Override
void close();
@Override
void startPreview();
@Override
void stopPreview();
@Override
void setDisplaySurface(Object displaySurface);
@Override
void setDisplayOrientation(int degrees);
@Override
void updateParameters(Parameters parameters);
@Override
Capabilities getCapabilities();
@Override
FocusResult autoFocus();
@Override
void measureExposure();
@Override
Photo takePicture();
@Override
PreviewStream getPreviewStream();
@Override
RendererParameters getRendererParameters();
@Override
List<LensPosition> getAvailableLensPositions();
}
@@ -0,0 +1,380 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import android.media.MediaRecorder
import android.support.annotation.FloatRange
import android.view.Surface
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.capability.provide.getCapabilities
import io.fotoapparat.characteristic.Characteristics
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.characteristic.toCameraId
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.orientation.computeDisplayOrientation
import io.fotoapparat.hardware.orientation.computeImageOrientation
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.apply.applyNewParameters
import io.fotoapparat.preview.Frame
import io.fotoapparat.preview.PreviewStream
import io.fotoapparat.result.FocusResult
import io.fotoapparat.result.Photo
import io.fotoapparat.view.Preview
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
typealias PreviewSize = io.fotoapparat.parameter.Resolution
/**
* Camera.
*/
internal open class CameraDevice(
private val logger: Logger,
val characteristics: Characteristics
) {
private lateinit var cameraParameters: CameraParameters
private lateinit var previewStream: PreviewStream
private lateinit var capabilities: Capabilities
private lateinit var surface: Surface
private lateinit var camera: Camera
private var cachedZoomParameters: Camera.Parameters? = null
private var displayRotation = 0
var imageRotation = 0
/**
* Opens a connection to a camera.
*/
open fun open() {
logger.recordMethod()
val lensPosition = characteristics.lensPosition
val cameraId = lensPosition.toCameraId()
try {
camera = Camera.open(cameraId)
capabilities = camera.getCapabilities()
previewStream = PreviewStream(camera)
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to open camera with lens position: $lensPosition and id: $cameraId",
cause = e
)
}
}
/**
* Closes the connection to a camera.
*/
open fun close() {
logger.recordMethod()
camera.release()
}
/**
* Starts preview.
*/
open fun startPreview() {
logger.recordMethod()
try {
camera.startPreview()
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to start preview for camera with lens " +
"position: ${characteristics.lensPosition} and id: ${characteristics.cameraId}",
cause = e
)
}
}
/**
* Stops preview.
*/
open fun stopPreview() {
logger.recordMethod()
camera.stopPreview()
}
/**
* Unlock camera.
*/
open fun unlock() {
logger.recordMethod()
camera.unlock()
}
/**
* Lock camera.
*/
open fun lock() {
logger.recordMethod()
camera.lock()
}
/**
* Invokes a still photo capture action.
*
* @return The captured photo.
*/
open fun takePhoto(): Photo {
logger.recordMethod()
return camera.takePhoto(imageRotation)
}
/**
* Returns the [Capabilities] of the camera.
*/
open fun getCapabilities(): Capabilities {
logger.recordMethod()
return capabilities
}
/**
* Returns the [CameraParameters] used.
*/
open fun getParameters(): CameraParameters {
logger.recordMethod()
return cameraParameters
}
/**
* Updates the desired camera parameters.
*/
open fun updateParameters(cameraParameters: CameraParameters) {
logger.recordMethod()
this.cameraParameters = cameraParameters
logger.log("New camera parameters are: $cameraParameters")
camera.updateParameters(cameraParameters)
}
/**
* Updates the frame processor.
*/
open fun updateFrameProcessor(frameProcessor: ((Frame) -> Unit)?) {
logger.recordMethod()
previewStream.updateProcessorSafely(frameProcessor)
}
/**
* Sets the current orientation of the display.
*/
open fun setDisplayOrientation(degrees: Int) {
logger.recordMethod()
imageRotation = computeImageOrientation(
degrees = degrees,
characteristics = characteristics
)
displayRotation = computeDisplayOrientation(
degrees = degrees,
characteristics = characteristics
)
logger.log("Image Rotation is: $imageRotation. Display rotation is: $displayRotation")
previewStream.frameOrientation = imageRotation
camera.setDisplayOrientation(displayRotation)
}
/**
* Changes zoom level of the camera. Must be called only if zoom is supported.
*
* @param level normalized zoom level. Value in range [0..1].
*/
open fun setZoom(@FloatRange(from = 0.0, to = 1.0) level: Float) {
logger.recordMethod()
setZoomSafely(level)
}
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
open fun autoFocus(): FocusResult {
logger.recordMethod()
return camera.focusSafely()
}
/**
* Sets the desired surface on which the camera's preview will be displayed.
*/
open fun setDisplaySurface(preview: Preview) {
logger.recordMethod()
surface = camera.setDisplaySurface(preview)
}
/**
* Attaches the camera to the [MediaRecorder].
*/
open fun attachRecordingCamera(mediaRecorder: MediaRecorder) {
logger.recordMethod()
mediaRecorder.setCamera(camera)
}
/**
* Returns the [Resolution] of the displayed preview.
*/
open fun getPreviewResolution(): Resolution {
logger.recordMethod()
val previewResolution = camera.getPreviewResolution(imageRotation)
logger.log("Preview resolution is: $previewResolution")
return previewResolution
}
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
try {
setZoomUnsafe(level)
} catch (e: Exception) {
logger.log("Unable to change zoom level to " + level + " e: " + e.message)
}
}
private fun setZoomUnsafe(@FloatRange(from = 0.0, to = 1.0) level: Float) {
(cachedZoomParameters ?: camera.parameters)
.apply {
zoom = (maxZoom * level).toInt()
}
.let {
cachedZoomParameters = it
camera.parameters = it
}
}
private fun Camera.focusSafely(): FocusResult {
val latch = CountDownLatch(1)
try {
autoFocus { _, _ -> latch.countDown() }
} catch (e: Exception) {
logger.log("Failed to perform autofocus using device ${characteristics.cameraId} e: ${e.message}")
return FocusResult.UnableToFocus
}
try {
latch.await(AUTOFOCUS_TIMEOUT_SECONDS, TimeUnit.SECONDS)
} catch (e: InterruptedException) {
// Do nothing
}
return FocusResult.Focused
}
}
private const val AUTOFOCUS_TIMEOUT_SECONDS = 3L
private fun Camera.takePhoto(imageRotation: Int): Photo {
val latch = CountDownLatch(1)
val photoReference = AtomicReference<Photo>()
takePicture(
null,
null,
null,
Camera.PictureCallback { data, _ ->
photoReference.set(
Photo(data, imageRotation)
)
latch.countDown()
}
)
latch.await()
return photoReference.get()
}
private fun computeImageOrientation(
degrees: Int,
characteristics: Characteristics
) = computeImageOrientation(
screenRotationDegrees = degrees,
cameraRotationDegrees = characteristics.orientation,
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
)
private fun computeDisplayOrientation(
degrees: Int,
characteristics: Characteristics
) = computeDisplayOrientation(
screenRotationDegrees = degrees,
cameraRotationDegrees = characteristics.orientation,
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
)
private fun Camera.updateParameters(newParameters: CameraParameters) {
parameters = parameters.applyNewParameters(newParameters)
}
@Throws(IOException::class)
private fun Camera.setDisplaySurface(
preview: Preview
): Surface = when (preview) {
is Preview.Texture -> preview.surfaceTexture
.also {
setPreviewTexture(it)
}
.let {
Surface(it)
}
is Preview.Surface -> preview.surfaceHolder
.also {
setPreviewDisplay(it)
}
.surface
}
private fun Camera.getPreviewResolution(imageRotation: Int): Resolution {
val previewSize = parameters.previewSize
val size = PreviewSize(
previewSize.width,
previewSize.height
)
return size.run {
when (imageRotation) {
0, 180 -> this
else -> flipDimensions()
}
}
}
private fun PreviewStream.updateProcessorSafely(frameProcessor: ((Frame) -> Unit)?) {
clearProcessors()
when (frameProcessor) {
null -> stop()
else -> {
addProcessor(frameProcessor)
start()
}
}
}
@@ -1,14 +0,0 @@
package io.fotoapparat.hardware;
/**
* A generic camera exception.
*/
public class CameraException extends RuntimeException {
public CameraException(Exception e) {
super(e);
}
public CameraException(String message) {
super(message);
}
}
@@ -1,109 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.NonNull;
import java.util.Collections;
import java.util.Set;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Size;
/**
* Capabilities of camera hardware.
*/
public class Capabilities {
@NonNull
private final Set<Size> photoSizes;
@NonNull
private final Set<Size> previewSizes;
@NonNull
private final Set<FocusMode> focusModes;
@NonNull
private final Set<Flash> flashModes;
public Capabilities(@NonNull Set<Size> photoSizes,
@NonNull Set<Size> previewSizes,
@NonNull Set<FocusMode> focusModes,
@NonNull Set<Flash> flashModes) {
this.photoSizes = photoSizes;
this.previewSizes = previewSizes;
this.focusModes = focusModes;
this.flashModes = flashModes;
}
/**
* @return Empty {@link Capabilities}.
*/
public static Capabilities empty() {
return new Capabilities(
Collections.<Size>emptySet(),
Collections.<Size>emptySet(),
Collections.<FocusMode>emptySet(),
Collections.<Flash>emptySet()
);
}
/**
* @return list of supported picture sizes.
*/
public Set<Size> supportedPictureSizes() {
return photoSizes;
}
/**
* @return list of supported preview sizes;
*/
public Set<Size> supportedPreviewSizes() {
return previewSizes;
}
/**
* @return list of supported focus modes.
*/
public Set<FocusMode> supportedFocusModes() {
return focusModes;
}
/**
* @return list of supported flash firing modes.
*/
public Set<Flash> supportedFlashModes() {
return flashModes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Capabilities that = (Capabilities) o;
return photoSizes.equals(that.photoSizes)
&& previewSizes.equals(that.previewSizes)
&& focusModes.equals(that.focusModes)
&& flashModes.equals(that.flashModes);
}
@Override
public int hashCode() {
int result = photoSizes.hashCode();
result = 31 * result + previewSizes.hashCode();
result = 31 * result + focusModes.hashCode();
result = 31 * result + flashModes.hashCode();
return result;
}
@Override
public String toString() {
return "Capabilities{" +
"photoSizes=" + photoSizes +
", previewSizes=" + previewSizes +
", focusModes=" + focusModes +
", flashModes=" + flashModes +
'}';
}
}
@@ -0,0 +1,193 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.characteristic.getCharacteristics
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.exception.camera.UnsupportedLensException
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.provide.getCameraParameters
import io.fotoapparat.preview.Frame
import io.fotoapparat.view.CameraRenderer
import kotlinx.coroutines.experimental.CompletableDeferred
/**
* Phone.
*/
internal open class Device(
private val logger: Logger,
private val display: Display,
open val scaleType: ScaleType,
open val cameraRenderer: CameraRenderer,
numberOfCameras: Int = Camera.getNumberOfCameras(),
initialConfiguration: CameraConfiguration,
initialLensPositionSelector: Collection<LensPosition>.() -> LensPosition?
) {
private val cameras = (0 until numberOfCameras).map { cameraId ->
CameraDevice(
logger = logger,
characteristics = getCharacteristics(cameraId)
)
}
private var lensPositionSelector: Collection<LensPosition>.() -> LensPosition? = initialLensPositionSelector
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
private var savedConfiguration = CameraConfiguration.default()
init {
updateLensPositionSelector(initialLensPositionSelector)
savedConfiguration = initialConfiguration
}
/**
* Selects a camera.
*/
open fun canSelectCamera(lensPositionSelector: (Collection<LensPosition>) -> LensPosition?): Boolean {
val selectedCameraDevice = selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
return selectedCameraDevice != null
}
/**
* Selects a camera. Will do nothing if camera cannot be selected.
*/
open fun selectCamera() {
logger.recordMethod()
selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
?.let {
selectedCameraDevice.complete(it)
}
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
}
/**
* Clears the selected camera.
*/
open fun clearSelectedCamera() {
selectedCameraDevice = CompletableDeferred()
}
/**
* Waits and returns the selected camera.
*/
open suspend fun awaitSelectedCamera(): CameraDevice {
return selectedCameraDevice.await()
}
/**
* Returns the selected camera.
*
* @throws IllegalStateException If no camera has been yet selected.
* @throws UnsupportedLensException If no camera could get selected.
*/
open fun getSelectedCamera(): CameraDevice {
return try {
selectedCameraDevice.getCompleted()
} catch (e: IllegalStateException) {
throw IllegalStateException("Camera has not started!")
}
}
/**
* @return `true` if a camera has been selected.
*/
open fun hasSelectedCamera() = selectedCameraDevice.isCompleted
/**
* @return rotation of the screen in degrees.
*/
open fun getScreenRotation(): Int {
return display.getRotation()
}
/**
* Updates the desired from the user camera lens position.
*/
open fun updateLensPositionSelector(newLensPosition: Collection<LensPosition>.() -> LensPosition?) {
logger.recordMethod()
lensPositionSelector = newLensPosition
}
/**
* Updates the desired from the user selectors.
*/
open fun updateConfiguration(newConfiguration: Configuration) {
logger.recordMethod()
savedConfiguration = updateConfiguration(
savedConfiguration = savedConfiguration,
newConfiguration = newConfiguration
)
}
/**
* @return The desired from the user selectors.
*/
open fun getConfiguration(): CameraConfiguration {
return savedConfiguration
}
open fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters {
return getCameraParameters(
cameraConfiguration = savedConfiguration,
capabilities = cameraDevice.getCapabilities()
)
}
open fun getFrameProcessor(): (Frame) -> Unit {
return savedConfiguration.frameProcessor
}
/**
* @return The desired from the user camera lens position.
*/
open fun getLensPositionSelector(): Collection<LensPosition>.() -> LensPosition? {
return lensPositionSelector
}
}
/**
* Updates the device's configuration.
*/
internal fun updateConfiguration(
savedConfiguration: CameraConfiguration,
newConfiguration: Configuration
) = CameraConfiguration(
flashMode = newConfiguration.flashMode ?: savedConfiguration.flashMode,
focusMode = newConfiguration.focusMode ?: savedConfiguration.focusMode,
frameProcessor = newConfiguration.frameProcessor ?: savedConfiguration.frameProcessor,
previewFpsRange = newConfiguration.previewFpsRange ?: savedConfiguration.previewFpsRange,
sensorSensitivity = newConfiguration.sensorSensitivity ?: savedConfiguration.sensorSensitivity,
pictureResolution = newConfiguration.pictureResolution ?: savedConfiguration.pictureResolution,
previewResolution = newConfiguration.previewResolution ?: savedConfiguration.previewResolution
)
/**
* Selects a camera from the set of available ones.
*/
internal fun selectCamera(
availableCameras: List<CameraDevice>,
lensPositionSelector: Collection<LensPosition>.() -> LensPosition?
): CameraDevice? {
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
val desiredPosition = lensPositionSelector(lensPositions)
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
}
@@ -0,0 +1,48 @@
package io.fotoapparat.hardware
import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executors
private var taskExecutor = Executors.newSingleThreadExecutor()
get() = field.takeUnless { it.isShutdown } ?: Executors.newSingleThreadExecutor().also { field = it }
private val cameraExecutor = Executors.newSingleThreadExecutor()
private val loggingExecutor = Executors.newSingleThreadExecutor()
private val mainThreadHandler = Handler(Looper.getMainLooper())
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.result.PendingResult].
*/
internal val pendingResultExecutor = Executors.newSingleThreadExecutor()
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.preview.PreviewStream].
*/
internal val frameProcessingExecutor = Executors.newSingleThreadExecutor()
/**
* Shuts down all pending camera tasks.
*/
internal fun shutdownPendingTasks() {
taskExecutor.shutdownNow()
}
/**
* Executes a camera task.
*/
internal fun executeTask(function: Runnable) = taskExecutor.execute(function)
/**
* Executes a camera operation.
*/
internal fun execute(function: () -> Unit) = cameraExecutor.execute(function)
/**
* Executes an operation in the main thread.
*/
internal fun executeLoggingThread(function: () -> Unit) = loggingExecutor.execute { function() }
/**
* Executes an operation in the main thread.
*/
internal fun executeMainThread(function: () -> Unit) = mainThreadHandler.post { function() }
@@ -0,0 +1,27 @@
package io.fotoapparat.hardware.display
import android.content.Context
import android.view.Surface
import android.view.WindowManager
/**
* A phone's display.
*/
open internal class Display(context: Context) {
private val display = context.getDisplay()
/**
* Returns the rotation of the screen from its "natural" orientation in degrees.
*/
open fun getRotation(): Int = when (display.rotation) {
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
Surface.ROTATION_0 -> 0
else -> 0
}
}
private fun Context.getDisplay() = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
@@ -1,16 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.lens.FocusResult;
/**
* Performs auto focus.
*/
public interface AutoFocusOperator {
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
FocusResult autoFocus();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.hardware.Capabilities;
/**
* An interface which indicates that the class can
* provide the camera capabilities.
*/
public interface CapabilitiesOperator {
/**
* Returns the {@link Capabilities} of the opened camera.
*
* @return The {@link Capabilities} of the camera.
*/
Capabilities getCapabilities();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.photo.Photo;
/**
* An interface which indicates that the class can
* capture still pictures.
*/
public interface CaptureOperator {
/**
* Invokes a still picture capture action.
*
* @return The captured photo.
*/
Photo takePicture();
}
@@ -1,23 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.LensPosition;
/**
* An interface which indicates that the class can
* open and close connections to a camera.
*/
public interface ConnectionOperator {
/**
* Opens a connection to a camera.
*
* @param lensPosition The camera position relatively to the screen of the device,
* which will determine which camera to open.
*/
void open(LensPosition lensPosition);
/**
* Closes the connection to a camera.
*/
void close();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* Measures the exposure.
*/
public interface ExposureMeasurementOperator {
/**
* Measures the exposure. This is a blocking operation which returns when measurement completes.
*/
void measureExposure();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* handle the orientation updates.
*/
public interface OrientationOperator {
/**
* Sets the current orientation of the display.
*/
void setDisplayOrientation(int degrees);
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.Parameters;
/**
* An interface which indicates that the class can
* support parameter updates.
*/
public interface ParametersOperator {
/**
* Updates the desired parameters for the preview and the photo capture actions.
*/
void updateParameters(Parameters parameters);
}
@@ -1,21 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* start and stop the capture preview.
*/
public interface PreviewOperator {
/**
* Starts the preview to the surface.
* <p>
* {@link #stopPreview()} should be called
* to stop the operation.
*/
void startPreview();
/**
* Stops the preview from the surface.
*/
void stopPreview();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.preview.PreviewStream;
/**
* Provides {@link PreviewStream} controlled by camera.
*/
public interface PreviewStreamOperator {
/**
* @return {@link PreviewStream} associated with camera.
*/
PreviewStream getPreviewStream();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.RendererParameters;
/**
* Builds {@link RendererParameters}.
*/
public interface RendererParametersOperator {
/**
* @return {@link RendererParameters}.
*/
RendererParameters getRendererParameters();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* set a preview surface.
*/
public interface SurfaceOperator {
/**
* Sets the desired surface on which the camera preview will be displayed.
*/
void setDisplaySurface(Object displaySurface);
}
@@ -0,0 +1,61 @@
package io.fotoapparat.hardware.orientation
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
* front cameras). `false` if it is not mirrored.
*
* @return clockwise rotation of the image relatively to current device orientation.
*/
internal fun computeImageOrientation(
screenRotationDegrees: Int,
cameraRotationDegrees: Int,
cameraIsMirrored: Boolean
): Int {
val rotation = if (cameraIsMirrored) {
-(screenRotationDegrees + cameraRotationDegrees)
} else {
screenRotationDegrees - cameraRotationDegrees
}
return (rotation + 720) % 360
}
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
* front cameras). `false` if it is not mirrored.
*
* @return display orientation in which user will see the output camera in a correct rotation.
*/
internal fun computeDisplayOrientation(
screenRotationDegrees: Int,
cameraRotationDegrees: Int,
cameraIsMirrored: Boolean
): Int {
var degrees = toClosestRightAngle(screenRotationDegrees)
return if (cameraIsMirrored) {
degrees = (cameraRotationDegrees + degrees) % 360
(360 - degrees) % 360
} else {
(cameraRotationDegrees - degrees + 360) % 360
}
}
/**
* @return closest right angle to given value. That is: 0, 90, 180, 270.
*/
internal fun toClosestRightAngle(degrees: Int): Int {
val roundUp = degrees % 90 > 45
val roundAppModifier = if (roundUp) 1 else 0
return (degrees / 90 + roundAppModifier) * 90 % 360
}
@@ -1,61 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.support.annotation.NonNull;
/**
* Monitors orientation of the device.
*/
public class OrientationSensor implements RotationListener.Listener {
private final RotationListener rotationListener;
private final ScreenOrientationProvider screenOrientationProvider;
private int lastKnownRotation;
private Listener listener;
public OrientationSensor(@NonNull final RotationListener rotationListener,
@NonNull final ScreenOrientationProvider screenOrientationProvider) {
this.rotationListener = rotationListener;
this.screenOrientationProvider = screenOrientationProvider;
rotationListener.setRotationListener(this);
}
/**
* Starts monitoring device's orientation.
*/
public void start(Listener listener) {
this.listener = listener;
rotationListener.enable();
}
/**
* Stops monitoring device's orientation.
*/
public void stop() {
rotationListener.disable();
listener = null;
}
@Override
public void onRotationChanged() {
if (listener != null) {
int rotation = screenOrientationProvider.getScreenRotation();
if (rotation != lastKnownRotation) {
listener.onOrientationChanged(rotation);
lastKnownRotation = rotation;
}
}
}
/**
* Notified when orientation of the device is updated.
*/
public interface Listener {
/**
* Called when orientation of the device is updated.
*/
void onOrientationChanged(int degrees);
}
}
@@ -0,0 +1,53 @@
package io.fotoapparat.hardware.orientation
import android.content.Context
import io.fotoapparat.hardware.Device
/**
* Monitors orientation of the device.
*/
open internal class OrientationSensor(
private val rotationListener: RotationListener,
private val device: Device
) {
constructor(context: Context,
device: Device
) : this(
RotationListener(context),
device
)
private val onOrientationChanged = {
device.getScreenRotation().let {
if (it != lastKnownRotation) {
listener(it)
lastKnownRotation = it
}
}
}
init {
rotationListener.orientationChanged = onOrientationChanged
}
private lateinit var listener: (Int) -> Unit
private var lastKnownRotation: Int = 0
/**
* Starts monitoring device's orientation.
*/
open fun start(listener: (Int) -> Unit) {
this.listener = listener
rotationListener.enable()
}
/**
* Stops monitoring device's orientation.
*/
open fun stop() {
rotationListener.disable()
}
}
@@ -1,64 +0,0 @@
package io.fotoapparat.hardware.orientation;
/**
* Utilities for working with device orientation.
*/
public class OrientationUtils {
/**
* @return closest right angle to given value. That is: 0, 90, 180, 270.
*/
public static int toClosestRightAngle(int degrees) {
boolean roundUp = degrees % 90 > 45;
int roundAppModifier = roundUp ? 1 : 0;
return (((degrees / 90) + roundAppModifier) * 90) % 360;
}
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
* for front cameras). {@code false} if it is not mirrored.
* @return display orientation in which user will see the output camera in a correct rotation.
*/
public static int computeDisplayOrientation(int screenRotationDegrees,
int cameraRotationDegrees,
boolean cameraIsMirrored) {
int degrees = OrientationUtils.toClosestRightAngle(screenRotationDegrees);
if (cameraIsMirrored) {
degrees = (cameraRotationDegrees + degrees) % 360;
degrees = (360 - degrees) % 360;
} else {
degrees = (cameraRotationDegrees - degrees + 360) % 360;
}
return degrees;
}
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
* for front cameras). {@code false} if it is not mirrored.
* @return clockwise rotation of the image relatively to current device orientation.
*/
public static int computeImageOrientation(int screenRotationDegrees,
int cameraRotationDegrees,
boolean cameraIsMirrored) {
int rotation;
if (cameraIsMirrored) {
rotation = -(screenRotationDegrees + cameraRotationDegrees);
} else {
rotation = screenRotationDegrees - cameraRotationDegrees;
}
return (rotation + 360 + 360) % 360;
}
}
@@ -1,44 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.OrientationEventListener;
/**
* Wrapper around {@link OrientationEventListener} to notify when the device's rotation has changed.
*/
public class RotationListener extends OrientationEventListener {
private Listener listener;
public RotationListener(Context context) {
super(context);
}
@Override
public void onOrientationChanged(int orientation) {
if (listener != null && canDetectOrientation()) {
listener.onRotationChanged();
}
}
/**
* Sets a listener to this class to notify future rotation events.
*
* @param listener The new listener
*/
void setRotationListener(@NonNull Listener listener) {
this.listener = listener;
}
/**
* Notified when the rotation of the device is updated.
*/
interface Listener {
/**
* Called when the rotation of the device has changed.
*/
void onRotationChanged();
}
}
@@ -0,0 +1,22 @@
package io.fotoapparat.hardware.orientation
import android.content.Context
import android.view.OrientationEventListener
/**
* Wrapper around [OrientationEventListener] to notify when the device's rotation has changed.
*/
open internal class RotationListener(
context: Context
) : OrientationEventListener(context) {
lateinit var orientationChanged: () -> Unit
override fun onOrientationChanged(orientation: Int) {
if (canDetectOrientation()) {
orientationChanged()
}
}
}
@@ -1,39 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
/**
* Provides orientation of the screen.
*/
public class ScreenOrientationProvider {
private final Display display;
public ScreenOrientationProvider(@NonNull Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
}
/**
* @return rotation of the screen in degrees.
*/
public int getScreenRotation() {
int rotation = display.getRotation();
switch (rotation) {
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
case Surface.ROTATION_0:
default:
return 0;
}
}
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.provider;
import java.util.List;
import io.fotoapparat.parameter.LensPosition;
/**
* Provides list of {@link LensPosition} which are available on the device.
*/
public interface AvailableLensPositionsProvider {
/**
* @return list of {@link LensPosition} which are available on the device.
*/
List<LensPosition> getAvailableLensPositions();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.log.Logger;
/**
* Abstraction for providing camera.
*/
public interface CameraProvider {
/**
* @return a {@link CameraDevice}.
*/
CameraDevice get(Logger logger);
}
@@ -1,38 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
/**
* Static factory for {@link CameraProvider}
*/
public class CameraProviders {
private CameraProviders() {
}
/**
* @return provider which uses Camera v2 on devices newer than Lollipop and falls back to Camera
* v1 on older devices.
*/
public static CameraProvider defaultProvider(Context context) {
return new DefaultProvider(
v1(),
v2(context)
);
}
/**
* @return provider for Camera v1.
*/
public static CameraProvider v1() {
return new V1Provider();
}
/**
* @return provider for Camera v2.
*/
public static CameraProvider v2(Context context) {
return new V2Provider(context);
}
}
@@ -1,35 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.log.Logger;
import io.fotoapparat.util.SDKInfo;
/**
* Selects correct version of providers for a device.
*/
public class DefaultProvider implements CameraProvider {
private final CameraProvider v1Provider;
private final CameraProvider v2Provider;
private final SDKInfo sdkInfo;
public DefaultProvider(CameraProvider v1Provider,
CameraProvider v2Provider) {
this(v1Provider, v2Provider, SDKInfo.getInstance());
}
DefaultProvider(CameraProvider v1Provider,
CameraProvider v2Provider,
SDKInfo sdkInfo) {
this.v1Provider = v1Provider;
this.v2Provider = v2Provider;
this.sdkInfo = sdkInfo;
}
@Override
public CameraDevice get(Logger logger) {
return sdkInfo.isBellowLollipop()
? v1Provider.get(logger)
: v2Provider.get(logger);
}
}
@@ -1,37 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.hardware.Camera;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import io.fotoapparat.parameter.LensPosition;
/**
* Provides available {@link LensPosition} using Camera v1 API.
*/
@SuppressWarnings("deprecation")
public class V1AvailableLensPositionProvider implements AvailableLensPositionsProvider {
@Override
public List<LensPosition> getAvailableLensPositions() {
HashSet<LensPosition> positions = new HashSet<>();
final int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(i, cameraInfo);
positions.add(
cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
? LensPosition.FRONT
: LensPosition.BACK
);
}
return new ArrayList<>(positions);
}
}
@@ -1,16 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.v1.Camera1;
import io.fotoapparat.log.Logger;
/**
* Always provides {@link Camera1}.
*/
public class V1Provider implements CameraProvider {
@Override
public CameraDevice get(Logger logger) {
return new Camera1(logger);
}
}
@@ -1,63 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.fotoapparat.hardware.CameraException;
import io.fotoapparat.parameter.LensPosition;
import static io.fotoapparat.hardware.v2.parameters.converters.LensPositionConverter.toLensPosition;
/**
* Provides available {@link LensPosition} using Camera v2 API.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class V2AvailableLensPositionProvider implements AvailableLensPositionsProvider {
private final CameraManager manager;
public V2AvailableLensPositionProvider(Context context) {
manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}
@Override
public List<LensPosition> getAvailableLensPositions() {
Set<LensPosition> positions = new HashSet<>();
String[] cameraIdList = getCameraIdListUnsafe();
for (String cameraId : cameraIdList) {
Integer lensFacingConstant = getLensPositionUnsafe(cameraId);
positions.add(toLensPosition(lensFacingConstant));
}
return new ArrayList<>(positions);
}
private Integer getLensPositionUnsafe(String cameraId) {
try {
return manager
.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.LENS_FACING);
} catch (CameraAccessException e) {
throw new CameraException(e);
}
}
private String[] getCameraIdListUnsafe() {
try {
return manager.getCameraIdList();
} catch (CameraAccessException e) {
throw new CameraException(e);
}
}
}
@@ -1,146 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.v2.Camera2;
import io.fotoapparat.hardware.v2.CameraThread;
import io.fotoapparat.hardware.v2.capabilities.CapabilitiesFactory;
import io.fotoapparat.hardware.v2.connection.CameraConnection;
import io.fotoapparat.hardware.v2.lens.executors.CaptureOperatorImpl;
import io.fotoapparat.hardware.v2.lens.executors.ExposureGatheringExecutor;
import io.fotoapparat.hardware.v2.lens.executors.FocusExecutor;
import io.fotoapparat.hardware.v2.lens.operations.LensOperationsFactory;
import io.fotoapparat.hardware.v2.orientation.OrientationManager;
import io.fotoapparat.hardware.v2.parameters.CaptureRequestFactory;
import io.fotoapparat.hardware.v2.parameters.ParametersProvider;
import io.fotoapparat.hardware.v2.parameters.RendererParametersProvider;
import io.fotoapparat.hardware.v2.readers.ContinuousSurfaceReader;
import io.fotoapparat.hardware.v2.readers.StillSurfaceReader;
import io.fotoapparat.hardware.v2.selection.CameraSelector;
import io.fotoapparat.hardware.v2.session.SessionManager;
import io.fotoapparat.hardware.v2.session.SessionProvider;
import io.fotoapparat.hardware.v2.stream.PreviewStream2;
import io.fotoapparat.hardware.v2.surface.TextureManager;
import io.fotoapparat.log.Logger;
/**
* Always provides {@link Camera2}.
*/
public class V2Provider implements CameraProvider {
private static final CameraThread CAMERA_THREAD = new CameraThread();
private final Context context;
public V2Provider(Context context) {
this.context = context;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public CameraDevice get(Logger logger) {
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
AvailableLensPositionsProvider availableLensPositionsProvider = new V2AvailableLensPositionProvider(
context
);
CameraSelector cameraSelector = new CameraSelector(manager);
CameraConnection cameraConnection = new CameraConnection(
cameraSelector,
manager,
CAMERA_THREAD
);
ParametersProvider parametersProvider = new ParametersProvider();
OrientationManager orientationManager = new OrientationManager(
cameraConnection
);
StillSurfaceReader stillSurfaceReader = new StillSurfaceReader(
parametersProvider,
CAMERA_THREAD
);
ContinuousSurfaceReader continuousSurfaceReader = new ContinuousSurfaceReader(
parametersProvider,
CAMERA_THREAD
);
TextureManager textureManager = new TextureManager(
orientationManager,
parametersProvider
);
CaptureRequestFactory captureRequestFactory = new CaptureRequestFactory(
cameraConnection,
stillSurfaceReader,
textureManager,
parametersProvider
);
SessionProvider sessionProvider = new SessionProvider(
stillSurfaceReader,
cameraConnection,
captureRequestFactory,
textureManager,
CAMERA_THREAD
);
SessionManager sessionManager = new SessionManager(
cameraConnection,
sessionProvider
);
CapabilitiesFactory capabilitiesOperator = new CapabilitiesFactory(cameraConnection);
PreviewStream2 previewStream = new PreviewStream2(
continuousSurfaceReader,
parametersProvider,
logger
);
RendererParametersProvider rendererParametersOperator = new RendererParametersProvider(
parametersProvider,
orientationManager
);
LensOperationsFactory lensOperationsFactory = new LensOperationsFactory(
sessionManager,
captureRequestFactory,
CAMERA_THREAD
);
FocusExecutor focusExecutor = new FocusExecutor(
parametersProvider,
lensOperationsFactory
);
ExposureGatheringExecutor exposureGatheringExecutor = new ExposureGatheringExecutor(
lensOperationsFactory
);
CaptureOperatorImpl captureExecutor = new CaptureOperatorImpl(
lensOperationsFactory,
stillSurfaceReader,
orientationManager
);
return new Camera2(
logger,
cameraConnection,
sessionManager,
textureManager,
orientationManager,
parametersProvider,
capabilitiesOperator,
previewStream,
rendererParametersOperator,
focusExecutor,
exposureGatheringExecutor,
captureExecutor,
availableLensPositionsProvider
);
}
}
@@ -1,311 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import android.view.SurfaceView;
import android.view.TextureView;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.CameraException;
import io.fotoapparat.hardware.Capabilities;
import io.fotoapparat.hardware.orientation.OrientationUtils;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.hardware.provider.V1AvailableLensPositionProvider;
import io.fotoapparat.hardware.v1.capabilities.CapabilitiesFactory;
import io.fotoapparat.lens.FocusResult;
import io.fotoapparat.log.Logger;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
/**
* Camera hardware driver for v1 {@link Camera} API.
*/
@SuppressWarnings("deprecation")
public class Camera1 implements CameraDevice {
private final CapabilitiesFactory capabilitiesFactory;
private final ParametersConverter parametersConverter;
private final AvailableLensPositionsProvider availableLensPositionsProvider;
private final Logger logger;
private Camera camera;
private int cameraId;
private PreviewStream1 previewStream;
private Throwable lastStacktrace;
private int imageRotation;
public Camera1(Logger logger) {
this.capabilitiesFactory = new CapabilitiesFactory();
this.parametersConverter = new ParametersConverter();
this.availableLensPositionsProvider = new V1AvailableLensPositionProvider();
this.logger = logger;
}
@Override
public void open(LensPosition lensPosition) {
recordMethod();
try {
cameraId = cameraIdForLensPosition(lensPosition);
camera = Camera.open(cameraId);
previewStream = new PreviewStream1(camera);
} catch (RuntimeException e) {
throw new CameraException(e);
}
camera.setErrorCallback(new Camera.ErrorCallback() {
@Override
public void onError(int error, Camera camera) {
if (lastStacktrace != null) {
lastStacktrace.printStackTrace();
}
throw new IllegalStateException("Camera error code: " + error);
}
});
}
private int cameraIdForLensPosition(LensPosition lensPosition) {
int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = getCameraInfo(i);
if (info.facing == facingForLensPosition(lensPosition)) {
return i;
}
}
return 0;
}
private int facingForLensPosition(LensPosition lensPosition) {
switch (lensPosition) {
case FRONT:
return Camera.CameraInfo.CAMERA_FACING_FRONT;
case BACK:
return Camera.CameraInfo.CAMERA_FACING_BACK;
default:
throw new IllegalArgumentException("Camera is not supported: " + lensPosition);
}
}
@Override
public void close() {
recordMethod();
if (camera != null) {
camera.release();
}
}
@Override
public void startPreview() {
recordMethod();
camera.startPreview();
}
@Override
public void stopPreview() {
recordMethod();
camera.stopPreview();
}
@Override
public void setDisplaySurface(Object displaySurface) {
recordMethod();
try {
trySetDisplaySurface(displaySurface);
} catch (IOException e) {
throw new CameraException(e);
}
}
@Override
public void setDisplayOrientation(int degrees) {
recordMethod();
Camera.CameraInfo info = getCameraInfo(cameraId);
imageRotation = computeImageOrientation(degrees, info);
camera.setDisplayOrientation(
computeDisplayOrientation(degrees, info)
);
previewStream.setFrameOrientation(imageRotation);
}
private int computeDisplayOrientation(int screenRotationDegrees,
Camera.CameraInfo info) {
return OrientationUtils.computeDisplayOrientation(
screenRotationDegrees,
info.orientation,
info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
);
}
private int computeImageOrientation(int screenRotationDegrees,
Camera.CameraInfo info) {
return OrientationUtils.computeImageOrientation(
screenRotationDegrees,
info.orientation,
info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
);
}
@Override
public void updateParameters(Parameters parameters) {
recordMethod();
Camera.Parameters cameraParameters = parametersConverter.convert(
parameters,
camera.getParameters()
);
camera.setParameters(cameraParameters);
}
@Override
public Capabilities getCapabilities() {
recordMethod();
return capabilitiesFactory.fromParameters(
camera.getParameters()
);
}
private void trySetDisplaySurface(Object displaySurface) throws IOException {
if (displaySurface instanceof TextureView) {
camera.setPreviewTexture(((TextureView) displaySurface).getSurfaceTexture());
} else if (displaySurface instanceof SurfaceView) {
camera.setPreviewDisplay(((SurfaceView) displaySurface).getHolder());
} else {
throw new IllegalArgumentException("Unsupported display surface: " + displaySurface);
}
}
@Override
public Photo takePicture() {
recordMethod();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Photo> photoReference = new AtomicReference<>();
camera.takePicture(
null,
null,
null,
new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
photoReference.set(
new Photo(data, imageRotation)
);
latch.countDown();
}
}
);
try {
latch.await();
} catch (InterruptedException e) {
// Do nothing
}
return photoReference.get();
}
@Override
public PreviewStream getPreviewStream() {
recordMethod();
ensurePreviewStreamAvailable();
return previewStream;
}
private void ensurePreviewStreamAvailable() {
if (previewStream == null) {
throw new IllegalStateException("Preview stream is null. Make sure camera is opened.");
}
}
@Override
public RendererParameters getRendererParameters() {
recordMethod();
return new RendererParameters(
previewSize(),
imageRotation
);
}
@Override
public FocusResult autoFocus() {
recordMethod();
final CountDownLatch latch = new CountDownLatch(1);
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
// Do nothing
}
return FocusResult.successNoMeasurement();
}
@Override
public void measureExposure() {
// TODO: 30.04.17
}
@Override
public List<LensPosition> getAvailableLensPositions() {
return availableLensPositionsProvider.getAvailableLensPositions();
}
private Size previewSize() {
Camera.Size previewSize = camera.getParameters().getPreviewSize();
return new Size(
previewSize.width,
previewSize.height
);
}
@NonNull
private Camera.CameraInfo getCameraInfo(int id) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
return info;
}
private void recordMethod() {
lastStacktrace = new Exception();
logger.log(
lastStacktrace.getStackTrace()[1].getMethodName()
);
}
}
@@ -1,92 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.hardware.Camera;
import io.fotoapparat.hardware.v1.capabilities.FlashCapability;
import io.fotoapparat.hardware.v1.capabilities.FocusCapability;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.Size;
/**
* Converts {@link Parameters} to {@link Camera.Parameters}.
*/
@SuppressWarnings("deprecation")
public class ParametersConverter {
/**
* Converts {@link Parameters} to {@link Camera.Parameters}.
*
* @param parameters parameters which should be converted.
* @param output output value. It is required because of C-style API in Camera v1.
* @return same object which was passed as {@code output}, but filled with new parameters.
*/
public Camera.Parameters convert(Parameters parameters, Camera.Parameters output) {
for (Parameters.Type storedType : parameters.storedTypes()) {
applyParameter(
storedType,
parameters,
output
);
}
return output;
}
private void applyParameter(Parameters.Type type,
Parameters input,
Camera.Parameters output) {
switch (type) {
case FOCUS_MODE:
applyFocusMode(
(FocusMode) input.getValue(type),
output
);
break;
case FLASH:
applyFlash(
(Flash) input.getValue(type),
output
);
break;
case PICTURE_SIZE:
applyPictureSize(
(Size) input.getValue(type),
output
);
break;
case PREVIEW_SIZE:
applyPreviewSize(
(Size) input.getValue(type),
output
);
break;
}
}
private void applyPreviewSize(Size size,
Camera.Parameters output) {
output.setPreviewSize(size.width, size.height);
}
private void applyPictureSize(Size size,
Camera.Parameters output) {
output.setPictureSize(size.width, size.height);
}
private void applyFlash(Flash flash,
Camera.Parameters output) {
output.setFlashMode(
FlashCapability.toCode(flash)
);
}
private void applyFocusMode(FocusMode focusMode,
Camera.Parameters output) {
output.setFocusMode(
FocusCapability.toCode(focusMode)
);
}
}
@@ -1,120 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import java.util.LinkedHashSet;
import java.util.Set;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.preview.Frame;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.preview.PreviewStream;
/**
* {@link PreviewStream} of Camera v1.
*/
@SuppressWarnings("deprecation")
public class PreviewStream1 implements PreviewStream {
private final Camera camera;
private final Set<FrameProcessor> frameProcessors = new LinkedHashSet<>();
private Size previewSize = null;
private int frameOrientation = 0;
public PreviewStream1(Camera camera) {
this.camera = camera;
}
/**
* @param frameOrientation CW rotation of frames in degrees.
*/
public void setFrameOrientation(int frameOrientation) {
this.frameOrientation = frameOrientation;
}
@Override
public void addFrameToBuffer() {
camera.addCallbackBuffer(
allocateBuffer(camera.getParameters())
);
}
private byte[] allocateBuffer(Camera.Parameters parameters) {
ensureNv21Format(parameters);
Camera.Size previewSize = parameters.getPreviewSize();
this.previewSize = new Size(
previewSize.width,
previewSize.height
);
return new byte[bytesPerFrame(previewSize)];
}
private int bytesPerFrame(Camera.Size previewSize) {
return (previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(ImageFormat.NV21)) / 8;
}
private void ensureNv21Format(Camera.Parameters parameters) {
if (parameters.getPreviewFormat() != ImageFormat.NV21) {
throw new UnsupportedOperationException("Only NV21 preview format is supported");
}
}
@Override
public void addProcessor(@NonNull FrameProcessor processor) {
synchronized (frameProcessors) {
frameProcessors.add(processor);
}
}
@Override
public void removeProcessor(@NonNull FrameProcessor processor) {
synchronized (frameProcessors) {
frameProcessors.remove(processor);
}
}
@Override
public void start() {
addFrameToBuffer();
camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
synchronized (frameProcessors) {
dispatchFrame(data);
}
}
});
}
private void dispatchFrame(byte[] image) {
ensurePreviewSizeAvailable();
final Frame frame = new Frame(previewSize, image, frameOrientation);
for (final FrameProcessor frameProcessor : frameProcessors) {
frameProcessor.processFrame(frame);
}
returnFrameToBuffer(frame);
}
private void ensurePreviewSizeAvailable() {
if (previewSize == null) {
throw new IllegalStateException("previewSize is null. Frame was not added?");
}
}
private void returnFrameToBuffer(Frame frame) {
camera.addCallbackBuffer(
frame.image
);
}
}
@@ -1,91 +0,0 @@
package io.fotoapparat.hardware.v1.capabilities;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.fotoapparat.hardware.Capabilities;
import io.fotoapparat.hardware.v1.Camera1;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Size;
/**
* {@link Capabilities} of {@link Camera1}.
*/
@SuppressWarnings("deprecation")
public class CapabilitiesFactory {
/**
* @return {@link Capabilities} from given camera parameters.
*/
public Capabilities fromParameters(Camera.Parameters parameters) {
return new Capabilities(
extractPictureSizes(parameters),
extractPreviewSizes(parameters),
extractFocusModes(parameters),
extractFlashModes(parameters)
);
}
private Set<Size> extractPreviewSizes(Camera.Parameters parameters) {
return mapSizes(parameters.getSupportedPreviewSizes());
}
private Set<Size> extractPictureSizes(Camera.Parameters parameters) {
return mapSizes(parameters.getSupportedPictureSizes());
}
private Set<Size> mapSizes(Collection<Camera.Size> sizes) {
HashSet<Size> result = new HashSet<>();
for (Camera.Size size : sizes) {
result.add(new Size(
size.width,
size.height
));
}
return result;
}
private Set<Flash> extractFlashModes(Camera.Parameters parameters) {
HashSet<Flash> result = new HashSet<>();
for (String flashMode : supportedFlashModes(parameters)) {
result.add(
FlashCapability.toFlash(flashMode)
);
}
return result;
}
@NonNull
private List<String> supportedFlashModes(Camera.Parameters parameters) {
List<String> supportedFlashModes = parameters.getSupportedFlashModes();
return supportedFlashModes != null
? supportedFlashModes
: Collections.singletonList(Camera.Parameters.FLASH_MODE_OFF);
}
private Set<FocusMode> extractFocusModes(Camera.Parameters parameters) {
HashSet<FocusMode> result = new HashSet<>();
for (String focusMode : parameters.getSupportedFocusModes()) {
result.add(
FocusCapability.toFocusMode(focusMode)
);
}
result.add(FocusMode.FIXED);
return result;
}
}
@@ -1,50 +0,0 @@
package io.fotoapparat.hardware.v1.capabilities;
import android.hardware.Camera;
import java.util.HashMap;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.util.BidirectionalHashMap;
/**
* Maps between {@link Flash} and Camera v1 flash codes.
*/
@SuppressWarnings("deprecation")
public class FlashCapability {
private static final BidirectionalHashMap<String, Flash> CODE_TO_FLASH;
static {
HashMap<String, Flash> map = new HashMap<>();
map.put(Camera.Parameters.FLASH_MODE_AUTO, Flash.AUTO);
map.put(Camera.Parameters.FLASH_MODE_RED_EYE, Flash.AUTO_RED_EYE);
map.put(Camera.Parameters.FLASH_MODE_ON, Flash.ON);
map.put(Camera.Parameters.FLASH_MODE_TORCH, Flash.TORCH);
map.put(Camera.Parameters.FLASH_MODE_OFF, Flash.OFF);
CODE_TO_FLASH = new BidirectionalHashMap<>(map);
}
/**
* @param code code of flash mode as in {@link Camera.Parameters}.
* @return {@link Flash} from given camera code.
*/
public static Flash toFlash(String code) {
Flash flash = CODE_TO_FLASH.forward().get(code);
if (flash == null) {
return Flash.OFF;
}
return flash;
}
/**
* @param flash flash mode.
* @return code of the flash mode as in {@link Camera.Parameters}.
*/
public static String toCode(Flash flash) {
return CODE_TO_FLASH.reversed().get(flash);
}
}
@@ -1,57 +0,0 @@
package io.fotoapparat.hardware.v1.capabilities;
import android.annotation.SuppressLint;
import android.hardware.Camera;
import java.util.HashMap;
import java.util.Map;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.util.BidirectionalHashMap;
/**
* Maps between {@link FocusMode} and Camera v1 focus mode codes.
*/
@SuppressLint("UseSparseArrays")
@SuppressWarnings("deprecation")
public class FocusCapability {
private final static BidirectionalHashMap<String, FocusMode> CODE_TO_FOCUS_MODE;
static {
Map<String, FocusMode> map = new HashMap<>();
map.put(Camera.Parameters.FOCUS_MODE_AUTO, FocusMode.AUTO);
map.put(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, FocusMode.CONTINUOUS_FOCUS);
map.put(Camera.Parameters.FOCUS_MODE_MACRO, FocusMode.MACRO);
map.put(Camera.Parameters.FOCUS_MODE_EDOF, FocusMode.EDOF);
map.put(Camera.Parameters.FOCUS_MODE_INFINITY, FocusMode.INFINITY);
map.put(Camera.Parameters.FOCUS_MODE_FIXED, FocusMode.FIXED);
CODE_TO_FOCUS_MODE = new BidirectionalHashMap<>(map);
}
/**
* Converts a focus mode code to a {@link FocusMode}.
*
* @param code code of the focus mode as in {@link Camera.Parameters}.
* @return The {@link io.fotoapparat.Fotoapparat}'s camera {@link FocusMode}.
*/
public static FocusMode toFocusMode(String code) {
FocusMode focusMode = CODE_TO_FOCUS_MODE.forward().get(code);
if (focusMode == null) {
return FocusMode.FIXED;
}
return focusMode;
}
/**
* Converts a {@link FocusMode} to a focus mdoe code as in {@link Camera.Parameters}.
*
* @param focusMode The {@link io.fotoapparat.Fotoapparat}'s camera {@link FocusMode}.
* @return focus mode code as in {@link Camera.Parameters}.
*/
public static String toCode(FocusMode focusMode) {
return CODE_TO_FOCUS_MODE.reversed().get(focusMode);
}
}
@@ -1,182 +0,0 @@
package io.fotoapparat.hardware.v2;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.util.List;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.Capabilities;
import io.fotoapparat.hardware.operators.AutoFocusOperator;
import io.fotoapparat.hardware.operators.CapabilitiesOperator;
import io.fotoapparat.hardware.operators.CaptureOperator;
import io.fotoapparat.hardware.operators.ConnectionOperator;
import io.fotoapparat.hardware.operators.ExposureMeasurementOperator;
import io.fotoapparat.hardware.operators.OrientationOperator;
import io.fotoapparat.hardware.operators.ParametersOperator;
import io.fotoapparat.hardware.operators.PreviewOperator;
import io.fotoapparat.hardware.operators.RendererParametersOperator;
import io.fotoapparat.hardware.operators.SurfaceOperator;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.lens.FocusResult;
import io.fotoapparat.log.Logger;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
/**
* Camera hardware driver for v2 {@link Camera2} API.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2 implements CameraDevice {
private final Logger logger;
private final OrientationOperator orientationOperator;
private final SurfaceOperator surfaceOperator;
private final CapabilitiesOperator capabilitiesOperator;
private final ConnectionOperator connectionOperator;
private final ParametersOperator parametersOperator;
private final PreviewOperator previewOperator;
private final ExposureMeasurementOperator exposureMeasurementOperator;
private final CaptureOperator captureOperator;
private final PreviewStream previewStream;
private final RendererParametersOperator rendererParametersOperator;
private final AutoFocusOperator autoFocusOperator;
private final AvailableLensPositionsProvider availableLensPositionsProvider;
public Camera2(Logger logger,
ConnectionOperator connectionOperator,
PreviewOperator previewOperator,
SurfaceOperator surfaceOperator,
OrientationOperator orientationOperator,
ParametersOperator parametersOperator,
CapabilitiesOperator capabilitiesOperator,
PreviewStream previewStream,
RendererParametersOperator rendererParametersOperator,
AutoFocusOperator autoFocusOperator,
ExposureMeasurementOperator exposureMeasurementOperator,
CaptureOperator captureOperator,
AvailableLensPositionsProvider availableLensPositionsProvider) {
this.logger = logger;
this.connectionOperator = connectionOperator;
this.parametersOperator = parametersOperator;
this.previewOperator = previewOperator;
this.orientationOperator = orientationOperator;
this.surfaceOperator = surfaceOperator;
this.capabilitiesOperator = capabilitiesOperator;
this.exposureMeasurementOperator = exposureMeasurementOperator;
this.captureOperator = captureOperator;
this.previewStream = previewStream;
this.rendererParametersOperator = rendererParametersOperator;
this.autoFocusOperator = autoFocusOperator;
this.availableLensPositionsProvider = availableLensPositionsProvider;
}
@Override
public void open(LensPosition lensPosition) {
recordMethod();
connectionOperator.open(lensPosition);
}
@Override
public void close() {
recordMethod();
connectionOperator.close();
}
@Override
public void startPreview() {
recordMethod();
previewOperator.startPreview();
}
@Override
public void stopPreview() {
recordMethod();
previewOperator.stopPreview();
}
@Override
public void setDisplaySurface(Object displaySurface) {
recordMethod();
surfaceOperator.setDisplaySurface(displaySurface);
}
@Override
public void setDisplayOrientation(int degrees) {
recordMethod();
orientationOperator.setDisplayOrientation(degrees);
}
@Override
public void updateParameters(Parameters parameters) {
recordMethod();
parametersOperator.updateParameters(parameters);
}
@Override
public Capabilities getCapabilities() {
recordMethod();
return capabilitiesOperator.getCapabilities();
}
@Override
public FocusResult autoFocus() {
recordMethod();
return autoFocusOperator.autoFocus();
}
@Override
public void measureExposure() {
recordMethod();
exposureMeasurementOperator.measureExposure();
}
@Override
public Photo takePicture() {
recordMethod();
return captureOperator.takePicture();
}
@Override
public PreviewStream getPreviewStream() {
recordMethod();
return previewStream;
}
@Override
public RendererParameters getRendererParameters() {
recordMethod();
return rendererParametersOperator.getRendererParameters();
}
@Override
public List<LensPosition> getAvailableLensPositions() {
recordMethod();
return availableLensPositionsProvider.getAvailableLensPositions();
}
private void recordMethod() {
Exception lastStacktrace = new Exception();
logger.log(
lastStacktrace.getStackTrace()[1].getMethodName()
);
}
}
@@ -1,24 +0,0 @@
package io.fotoapparat.hardware.v2;
import android.os.Handler;
import android.os.HandlerThread;
/**
* Thread handler with looper to execute async camera2 operations.
*/
public class CameraThread extends HandlerThread {
public CameraThread() {
super("CameraThread");
start();
}
/**
* Creates a new handler for the this thread.
*
* @return the new handler for this thread.
*/
public Handler createHandler() {
return new Handler(getLooper());
}
}

Some files were not shown because too many files have changed in this diff Show More