Compare commits

...

107 Commits

Author SHA1 Message Date
Dionysis Lorentzos 9a8bbb1021 Update README.md 2018-01-20 22:17:00 +01:00
Dmitry Zaytsev b55ebc6c68 Catching IOException when assigning display surface 2018-01-20 22:04:31 +01:00
Dmitry Zaytsev 631d216b13 Merge pull request #187 from Fotoapparat/revert_callbacks
Add callback for java only users in builder
2018-01-20 21:08:59 +01:00
Dmitry Zaytsev a31a856b55 Merge pull request #188 from Fotoapparat/fix/camera-threading
Using only one thread for operating with camera
2018-01-20 20:53:52 +01:00
Dmitry Zaytsev ff265c5c73 Merge branch 'master' of github.com:Fotoapparat/Fotoapparat into fix/camera-threading 2018-01-20 18:41:50 +01:00
Dmitry Zaytsev 78f5ad88c2 Making sure that task queue does not remain polluted with tasks. 2018-01-20 18:39:47 +01:00
Dmitry Zaytsev 35e30c12fe Properly canceling tasks. 2018-01-20 18:33:26 +01:00
Dmitry Zaytsev d0fec5f5b9 Using CameraExecutor in routines 2018-01-20 12:54:51 +01:00
Dmitry Zaytsev 7233866fcc Implemented CameraExecutor 2018-01-20 12:39:57 +01:00
Dmitry Zaytsev c7e3c01b2f Removed task executor 2018-01-20 12:22:57 +01:00
Dionysis 3de625f2cd Revert changes 2018-01-19 19:45:55 +01:00
Dionysis 72dcec0470 Unassign focus listener on stop. Fixes #185 2018-01-19 17:58:43 +01:00
Dionysis 8104644cfb Add callback for java only users in builder 2018-01-19 17:56:46 +01:00
Dionysis 58ffc4f9ea Add forgotten var assignments. Fixes #183 2018-01-19 17:04:06 +01:00
Dmitry Zaytsev 31d48c6154 Merge branch 'master' of github.com:Fotoapparat/Fotoapparat 2018-01-18 18:26:30 +01:00
Dmitry Zaytsev db791d9259 Using stable version of gradle plugin 2018-01-18 18:26:21 +01:00
Dionysis Lorentzos 82752f13c6 Merge pull request #180 from Fotoapparat/feature/rotation
Even if the screen orientation is locked, the device will render corr…
2018-01-18 17:21:10 +01:00
Dionysis 8214559d98 Improve logs in pending result 2018-01-18 12:55:52 +01:00
Dionysis 32e479cc7b Add infinity in default camera configuration 2018-01-18 12:31:47 +01:00
Dionysis 262aa4ed9b Merge branch 'master' into feature/rotation
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/hardware/CameraDevice.kt
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Device.kt
#	fotoapparat/src/main/java/io/fotoapparat/preview/PreviewStream.kt
2018-01-11 23:06:32 +01:00
Dionysis 762fae3d22 Even if the screen orientation is locked, the device will render correctly the image rotation. 2018-01-11 23:01:36 +01:00
Dionysis Lorentzos d8c5cb6ada Merge pull request #176 from dewarder/fix-codestyle
Add typealiases. Codestyle improvements.
2018-01-11 21:20:50 +01:00
Artem Hluhovskyi 5125541274 Merge branch 'master' of https://github.com/Fotoapparat/Fotoapparat into fix-codestyle
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/exif/ExifWriter.kt
2018-01-11 18:48:25 +02:00
Artem Hluhovskyi da4ca3a699 Clean up some methods with apply() 2018-01-11 18:03:26 +02:00
Sandi Barisic 98dab42245 Accept rotation in degrees instead of Photos in ExifOrientationHelper 2018-01-11 15:37:57 +01:00
Artem Hluhovskyi e8dfac0fd6 Add typealiases for selectors. Clean up. 2018-01-11 14:41:39 +02:00
Dionysis Lorentzos 4e2c91a7e9 Update README.md 2018-01-10 12:45:31 +01:00
pb 3aba7f8803 Revert minimum SDK version to 14. 2018-01-10 12:41:58 +01:00
Dionysis 4cf6d16baa Bump tag 2018-01-08 22:19:47 +01:00
Dionysis cf22a9a1f8 Fix merge failure 2018-01-08 22:13:24 +01:00
Dionysis 6781e349f9 Bump version 2018-01-08 21:48:48 +01:00
Dionysis Lorentzos 08446ff533 Merge pull request #171 from Fotoapparat/manual_focus
Add focus on tap
2018-01-08 21:47:18 +01:00
Dionysis Lorentzos 9a45b8f400 Merge pull request #172 from Fotoapparat/capabilities_null
Wait capabilities + parameters to be created before obtaining them.
2018-01-08 15:34:58 +01:00
Dionysis e8092902d2 Add kdocs 2018-01-08 00:07:58 +01:00
Dionysis d679ff7454 Wait capabilities + parameters to be created before obtaining them. 2018-01-08 00:06:01 +01:00
Dionysis Lorentzos 4583b8d992 Merge branch 'master' into manual_focus 2018-01-07 23:27:31 +01:00
Dionysis 3da2615acd Add capture button 2018-01-07 23:23:16 +01:00
Dionysis 12c3c3a852 Fix logs 2018-01-07 23:06:21 +01:00
Dionysis 0458642f75 Add focus support 2018-01-07 23:01:27 +01:00
Dionysis cd6cebdb6c Photo has helping functionality to get the dimensions. Fixes #137 2018-01-03 02:56:39 +02:00
Dionysis 2513e42830 Notify exception from the callback instead of crashing the app. Fixes #164, #165 2018-01-03 02:37:49 +02:00
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
433 changed files with 9527 additions and 17419 deletions
+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
+81 -74
View File
@@ -12,22 +12,23 @@ What it provides:
- 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
@@ -45,48 +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
.previewScaleType(ScaleType.CENTER_CROP) // we want the preview to fill the view
.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()
}
```
@@ -94,60 +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.
```java
fotoapparat.updateParameters(
UpdateRequest.builder()
.flash(
isTurnedOn
? torch()
: off()
)
.build()
```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 multiple instances of Fotoapparat, for example an instance using the device's front camera and another using the back, `FotoapparatSwitcher` can be used:
In order to switch between cameras, `Fotoapparat.switchTo()` can be used with the new desired `lensPosition` and its `cameraConfiguration`.
```java
FotoapparatSwitcher fotoapparatSwitcher = FotoapparatSwitcher.withDefault(fotoapparatFront);
fotoapparatSwitcher.switchTo(fotoapparatBack);
```kotlin
fotoapparat.switchTo(
lensPosition = front(),
cameraConfiguration = newConfigurationForFrontCamera
)
```
## Set up
@@ -159,7 +166,7 @@ repositories {
maven { url 'https://jitpack.io' }
}
compile 'io.fotoapparat.fotoapparat:library:1.5.0'
compile 'io.fotoapparat.fotoapparat:library:2.2.0'
```
Camera permission will be automatically added to your `AndroidManifest.xml`. Do not forget to request this permission on Marshmallow and higher.
+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: 14,
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.0.1'
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
+14 -8
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'
}
@@ -26,9 +29,12 @@ android {
}
dependencies {
compile "com.android.support:support-annotations:${project.appCompatVersion}"
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'
testCompile "junit:junit:${project.junitVersion}"
testCompile "org.mockito:mockito-core:${project.mockitoVersion}"
testCompile 'commons-io:commons-io:2.5'
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,342 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.fotoapparat.error.Callbacks;
import io.fotoapparat.error.CameraErrorCallback;
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.CurrentParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersValidator;
import io.fotoapparat.parameter.update.UpdateRequest;
import io.fotoapparat.result.CapabilitiesResult;
import io.fotoapparat.result.FocusResult;
import io.fotoapparat.result.ParametersResult;
import io.fotoapparat.result.PendingResult;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.routine.CheckAvailabilityRoutine;
import io.fotoapparat.routine.ConfigurePreviewStreamRoutine;
import io.fotoapparat.routine.StartCameraRoutine;
import io.fotoapparat.routine.StopCameraRoutine;
import io.fotoapparat.routine.UpdateOrientationRoutine;
import io.fotoapparat.routine.focus.AutoFocusRoutine;
import io.fotoapparat.routine.parameter.UpdateParametersRoutine;
import io.fotoapparat.routine.picture.TakePictureRoutine;
import io.fotoapparat.routine.zoom.UpdateZoomLevelRoutine;
/**
* 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 CurrentParametersProvider currentParametersProvider;
private final TakePictureRoutine takePictureRoutine;
private final AutoFocusRoutine autoFocusRoutine;
private final CheckAvailabilityRoutine checkAvailabilityRoutine;
private final UpdateParametersRoutine updateParametersRoutine;
private final UpdateZoomLevelRoutine updateZoomLevelRoutine;
private final Executor executor;
private boolean started = false;
Fotoapparat(StartCameraRoutine startCameraRoutine,
StopCameraRoutine stopCameraRoutine,
UpdateOrientationRoutine updateOrientationRoutine,
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine,
CapabilitiesProvider capabilitiesProvider,
CurrentParametersProvider parametersProvider,
TakePictureRoutine takePictureRoutine,
AutoFocusRoutine autoFocusRoutine,
CheckAvailabilityRoutine checkAvailabilityRoutine,
UpdateParametersRoutine updateParametersRoutine,
UpdateZoomLevelRoutine updateZoomLevelRoutine,
Executor executor) {
this.startCameraRoutine = startCameraRoutine;
this.stopCameraRoutine = stopCameraRoutine;
this.updateOrientationRoutine = updateOrientationRoutine;
this.configurePreviewStreamRoutine = configurePreviewStreamRoutine;
this.capabilitiesProvider = capabilitiesProvider;
this.currentParametersProvider = parametersProvider;
this.takePictureRoutine = takePictureRoutine;
this.autoFocusRoutine = autoFocusRoutine;
this.checkAvailabilityRoutine = checkAvailabilityRoutine;
this.updateParametersRoutine = updateParametersRoutine;
this.updateZoomLevelRoutine = updateZoomLevelRoutine;
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) {
CameraErrorCallback cameraErrorCallback = Callbacks.onMainThread(
builder.cameraErrorCallback
);
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,
builder.previewFpsRangeSelector,
builder.sensorSensitivitySelector,
builder.jpegQuality,
parametersValidator
);
StartCameraRoutine startCameraRoutine = new StartCameraRoutine(
cameraDevice,
builder.renderer,
builder.scaleType,
builder.lensPositionSelector,
screenOrientationProvider,
initialParametersProvider,
cameraErrorCallback
);
StopCameraRoutine stopCameraRoutine = new StopCameraRoutine(cameraDevice);
OrientationSensor orientationSensor = new OrientationSensor(
rotationListener,
screenOrientationProvider
);
UpdateOrientationRoutine updateOrientationRoutine = new UpdateOrientationRoutine(
cameraDevice,
orientationSensor,
SERIAL_EXECUTOR,
builder.logger
);
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine = new ConfigurePreviewStreamRoutine(
cameraDevice,
builder.frameProcessor
);
CapabilitiesProvider capabilitiesProvider = new CapabilitiesProvider(
cameraDevice,
SERIAL_EXECUTOR
);
CurrentParametersProvider currentParametersProvider = new CurrentParametersProvider(
cameraDevice,
SERIAL_EXECUTOR
);
TakePictureRoutine takePictureRoutine = new TakePictureRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
AutoFocusRoutine autoFocusRoutine = new AutoFocusRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
CheckAvailabilityRoutine checkAvailabilityRoutine = new CheckAvailabilityRoutine(
cameraDevice,
builder.lensPositionSelector
);
UpdateParametersRoutine updateParametersRoutine = new UpdateParametersRoutine(
cameraDevice
);
UpdateZoomLevelRoutine updateZoomLevelRoutine = new UpdateZoomLevelRoutine(
cameraDevice
);
return new Fotoapparat(
startCameraRoutine,
stopCameraRoutine,
updateOrientationRoutine,
configurePreviewStreamRoutine,
capabilitiesProvider,
currentParametersProvider,
takePictureRoutine,
autoFocusRoutine,
checkAvailabilityRoutine,
updateParametersRoutine,
updateZoomLevelRoutine,
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();
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return {@link ParametersResult} which will deliver result asynchronously.
*/
public ParametersResult getCurrentParameters() {
ensureStarted();
return currentParametersProvider.getParameters();
}
/**
* 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() {
focus();
return this;
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*/
public PendingResult<FocusResult> focus() {
ensureStarted();
return autoFocusRoutine.autoFocus();
}
/**
* Asynchronously updates parameters of the camera. Must be called only after {@link #start()}.
*/
public void updateParameters(@NonNull final UpdateRequest updateRequest) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateParametersRoutine.updateParameters(updateRequest);
}
});
}
/**
* Asynchronously updates zoom level of the camera. Must be called only after {@link #start()}.
* <p>
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel zoom level of the camera. A value between 0 and 1.
*/
public void setZoom(@FloatRange(from = 0f, to = 1f) final float zoomLevel) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateZoomLevelRoutine.updateZoomLevel(zoomLevel);
}
});
}
/**
* 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,245 @@
package io.fotoapparat
import android.content.Context
import android.support.annotation.FloatRange
import io.fotoapparat.concurrent.CameraExecutor
import io.fotoapparat.concurrent.CameraExecutor.Operation
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.error.onMainThread
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
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.*
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.FocalPointSelector
/**
* Camera. Takes pictures.
*/
class Fotoapparat
@JvmOverloads constructor(
context: Context,
view: CameraRenderer,
focusView: FocalPointSelector? = null,
lensPosition: LensPositionSelector = firstAvailable(
back(),
front(),
external()
),
scaleType: ScaleType = ScaleType.CenterCrop,
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
cameraErrorCallback: CameraErrorCallback = {},
private val executor: CameraExecutor = EXECUTOR,
private val logger: Logger = none()
) {
private val mainThreadErrorCallback = cameraErrorCallback.onMainThread()
private val display = Display(context)
private val device = Device(
cameraRenderer = view,
focusPointSelector = focusView,
logger = logger,
display = display,
scaleType = scaleType,
initialLensPositionSelector = lensPosition,
initialConfiguration = cameraConfiguration,
executor = executor
)
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()
executor.execute(Operation {
device.bootStart(
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
})
}
/**
* Stops camera.
*
* @throws IllegalStateException If the camera has not started.
*/
fun stop() {
logger.recordMethod()
executor.cancelTasks()
executor.execute(Operation {
device.shutDown(
orientationSensor = orientationSensor
)
})
}
/**
* Takes picture, returns immediately.
*
* @return [PhotoResult] which will deliver result asynchronously.
*/
fun takePicture(): PhotoResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::takePhoto
))
return PhotoResult.fromFuture(future, logger)
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return [CapabilitiesResult] which will deliver result asynchronously.
*/
fun getCapabilities(): CapabilitiesResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::getCapabilities
))
return PendingResult.fromFuture(future, logger)
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return [ParametersResult] which will deliver result asynchronously.
*/
fun getCurrentParameters(): ParametersResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::getCurrentParameters
))
return PendingResult.fromFuture(future, logger)
}
/**
* Updates current configuration.
*
* @throws IllegalStateException If the current camera has not started.
*/
fun updateConfiguration(newConfiguration: Configuration) = executor.execute(
Operation(cancellable = true) {
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) = executor.execute(
Operation(cancellable = true) {
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 = apply {
logger.recordMethod()
focus()
}
/**
* 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 future = executor.execute(Operation(
cancellable = true,
function = device::focus
))
return PendingResult.fromFuture(future, logger)
}
/**
* Switches to another camera. If previous camera has already started then it will be
* stopped automatically and new will start.
*/
fun switchTo(
lensPosition: LensPositionSelector,
cameraConfiguration: CameraConfiguration
) {
logger.recordMethod()
executor.execute(Operation(cancellable = true) {
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: LensPositionSelector
): Boolean = device.canSelectCamera(selector)
companion object {
private val EXECUTOR = CameraExecutor()
@JvmStatic
fun with(context: Context): FotoapparatBuilder = FotoapparatBuilder(context)
}
}
@@ -1,220 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import io.fotoapparat.error.CameraErrorCallback;
import java.util.Collection;
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.ScaleType;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.selector.FlashSelectors;
import io.fotoapparat.parameter.selector.SelectorFunction;
import io.fotoapparat.parameter.selector.Selectors;
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<Collection<LensPosition>, LensPosition> lensPositionSelector = firstAvailable(
back(),
front(),
external()
);
SelectorFunction<Collection<Size>, Size> photoSizeSelector = biggestSize();
SelectorFunction<Collection<Size>, Size> previewSizeSelector = biggestSize();
SelectorFunction<Collection<FocusMode>, FocusMode> focusModeSelector = firstAvailable(
continuousFocus(),
autoFocus(),
fixed()
);
SelectorFunction<Collection<Flash>, Flash> flashSelector = FlashSelectors.off();
SelectorFunction<Collection<Range<Integer>>, Range<Integer>> previewFpsRangeSelector = Selectors.nothing();
SelectorFunction<Range<Integer>, Integer> sensorSensitivitySelector = Selectors.nothing();
Integer jpegQuality;
ScaleType scaleType = ScaleType.CENTER_CROP;
FrameProcessor frameProcessor = null;
Logger logger = Loggers.none();
CameraErrorCallback cameraErrorCallback = CameraErrorCallback.NULL;
FotoapparatBuilder(@NonNull Context context) {
this.context = context;
}
/**
* @param cameraProvider decides which {@link CameraDevice} to use.
*/
public FotoapparatBuilder cameraProvider(@NonNull CameraProvider cameraProvider) {
this.cameraProvider = cameraProvider;
return this;
}
/**
* @param selector selects size of the photo (in pixels) from list of available sizes.
*/
public FotoapparatBuilder photoSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
photoSizeSelector = selector;
return this;
}
/**
* @param selector selects size of preview stream (in pixels) from list of available sizes.
*/
public FotoapparatBuilder previewSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
previewSizeSelector = selector;
return this;
}
/**
* @param scaleType of preview inside the view.
*/
public FotoapparatBuilder previewScaleType(ScaleType scaleType) {
this.scaleType = scaleType;
return this;
}
/**
* @param selector selects focus mode from list of available modes.
*/
public FotoapparatBuilder focusMode(@NonNull SelectorFunction<Collection<FocusMode>, FocusMode> selector) {
focusModeSelector = selector;
return this;
}
/**
* @param selector selects flash mode from list of available modes.
*/
public FotoapparatBuilder flash(@NonNull SelectorFunction<Collection<Flash>, Flash> selector) {
flashSelector = selector;
return this;
}
/**
* @param selector camera sensor position from list of available positions.
*/
public FotoapparatBuilder lensPosition(@NonNull SelectorFunction<Collection<LensPosition>, LensPosition> selector) {
lensPositionSelector = selector;
return this;
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
public FotoapparatBuilder previewFpsRange(@NonNull SelectorFunction<Collection<Range<Integer>>, Range<Integer>> selector) {
previewFpsRangeSelector = selector;
return this;
}
/**
* @param selector selects ISO value from range of available values.
*/
public FotoapparatBuilder sensorSensitivity(@NonNull SelectorFunction<Range<Integer>, Integer> selector) {
sensorSensitivitySelector = selector;
return this;
}
/**
* @param jpegQuality of the picture (1-100)
*/
public FotoapparatBuilder jpegQuality(@IntRange(from=0,to=100) @NonNull Integer jpegQuality) {
this.jpegQuality = jpegQuality;
return this;
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
public FotoapparatBuilder frameProcessor(@NonNull 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(@NonNull Logger logger) {
this.logger = logger;
return this;
}
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorCallback
*/
public FotoapparatBuilder cameraErrorCallback(@NonNull CameraErrorCallback callback) {
this.cameraErrorCallback = callback;
return this;
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
public FotoapparatBuilder into(@NonNull 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,183 @@
package io.fotoapparat
import android.content.Context
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.error.CameraErrorListener
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.CameraView
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
/**
* Builder for [Fotoapparat].
*/
class FotoapparatBuilder internal constructor(private var context: Context) {
internal var lensPositionSelector: LensPositionSelector = firstAvailable(
back(),
front(),
external()
)
internal var cameraErrorCallback: CameraErrorCallback = {}
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: LensPositionSelector): FotoapparatBuilder =
apply { lensPositionSelector = selector }
/**
* @param scaleType of preview inside the view.
*/
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder =
apply { this.scaleType = scaleType }
/**
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
*/
fun photoResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
pictureResolution = selector
)
}
/**
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
*/
fun previewResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
previewResolution = selector
)
}
/**
* @param selector selects focus mode from list of available modes.
*/
fun focusMode(selector: FocusModeSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
focusMode = selector
)
}
/**
* @param selector selects flash mode from list of available modes.
*/
fun flash(selector: FlashSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
flashMode = selector
)
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
fun previewFpsRange(selector: FpsRangeSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
previewFpsRange = selector
)
}
/**
* @param selector selects ISO value from range of available values.
*/
fun sensorSensitivity(selector: SensorSensitivitySelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
sensorSensitivity = selector
)
}
/**
* @param selector of the Jpeg picture quality.
*/
fun jpegQuality(selector: QualitySelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
jpegQuality = selector
)
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessorJava
*/
fun frameProcessor(frameProcessor: FrameProcessor): FotoapparatBuilder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessorJava
*/
fun frameProcessor(frameProcessor: FrameProcessorJava): FotoapparatBuilder = apply {
configuration = configuration.copy(
frameProcessor = { frameProcessor.process(it) }
)
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see io.fotoapparat.log.Loggers
*/
fun logger(logger: Logger): FotoapparatBuilder =
apply { this.logger = logger }
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorListener
*/
fun cameraErrorCallback(callback: CameraErrorListener): FotoapparatBuilder =
apply { cameraErrorCallback = { callback.onError(it) } }
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorListener
*/
fun cameraErrorCallback(callback: CameraErrorCallback): FotoapparatBuilder =
apply { cameraErrorCallback = callback }
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
fun into(renderer: CameraRenderer): FotoapparatBuilder =
apply { this.renderer = renderer }
/**
* @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,57 @@
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 maxFocusAreas: Int,
val maxMeteringAreas: Int,
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() +
"maxFocusAreas:" + maxFocusAreas.wrap() +
"maxMeteringAreas:" + maxMeteringAreas.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,34 @@
@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() },
maxFocusAreas = maxNumFocusAreas,
canSmoothZoom = supportedSmoothZoom,
maxMeteringAreas = maxNumMeteringAreas,
jpegQualityRange = jpegQualityRange,
antiBandingModes = supportedAutoBandingModes.extract(String::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,21 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
import io.fotoapparat.hardware.orientation.toOrientation
/**
* Returns the [Characteristics] for the given `cameraId`.
*/
internal fun getCharacteristics(cameraId: Int): Characteristics {
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
val lensPosition = info.facing.toLensPosition()
return Characteristics(
cameraId = cameraId,
lensPosition = lensPosition,
cameraOrientation = info.orientation.toOrientation(),
isMirrored = lensPosition == LensPosition.Front
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.characteristic
/**
* A camera characteristic.
*/
interface Characteristic
@@ -0,0 +1,13 @@
package io.fotoapparat.characteristic
import io.fotoapparat.hardware.orientation.Orientation
/**
* A set of information about the camera.
*/
internal data class Characteristics(
val cameraId: Int,
val lensPosition: LensPosition,
val cameraOrientation: Orientation,
val isMirrored: Boolean
)
@@ -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,33 @@
@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 =
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 =
(0 until Camera.getNumberOfCameras())
.find { cameraId ->
this == getCharacteristics(cameraId).lensPosition
}
?: throw CameraException("Device has no camera for the desired lens position(s).")
@@ -0,0 +1,78 @@
package io.fotoapparat.concurrent
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
/**
* Executes camera related operations using a dedicated thread and provides a control over the queue
* of those operations.
*
* This class should be accessed only from a one thread at a time.
*/
class CameraExecutor(
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
) {
private val cancellableTasksQueue = LinkedList<Future<*>>()
/**
* Adds operation to the serial execution queue.
*/
fun <T> execute(operation: Operation<T>): Future<T> {
val future = executor.submit(Callable {
operation.function()
})
if (operation.cancellable) {
cancellableTasksQueue += future
}
cleanUpCancelledTasks()
return future
}
private fun cleanUpCancelledTasks() {
cancellableTasksQueue.removeAll {
!it.isPending
}
}
/**
* Cancels all cancellable tasks in the queue. Non-cancellable tasks would still be executed.
*
* After this operation is completed the executor is still in a valid state and can be used
* further.
*/
fun cancelTasks() {
cancellableTasksQueue
.filter { it.isPending }
.forEach {
it.cancel(true)
}
cancellableTasksQueue.clear()
}
private val Future<*>.isPending get() = !isCancelled && !isDone
/**
* Operation to be executed.
*/
data class Operation<out T>(
/**
* `true` if operation could be cancelled. `false` if operation can not be cancelled.
*/
val cancellable: Boolean = false,
/**
* Body of the operation.
*/
val function: () -> T
)
}
@@ -0,0 +1,123 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
private const val DEFAULT_JPEG_QUALITY = 90
/**
* A camera configuration which has all it's selectors defined.
*/
data class CameraConfiguration(
override val flashMode: FlashSelector = off(),
override val focusMode: FocusModeSelector = firstAvailable(
continuousFocusPicture(),
autoFocus(),
fixed(),
infinity()
),
override val jpegQuality: QualitySelector = manualJpegQuality(DEFAULT_JPEG_QUALITY),
override val frameProcessor: FrameProcessor = {},
override val previewFpsRange: FpsRangeSelector = highestFps(),
override val antiBandingMode: AntiBandingModeSelector = firstAvailable(
auto(),
hz50(),
hz60(),
none()
),
override val sensorSensitivity: SensorSensitivitySelector? = null,
override val pictureResolution: ResolutionSelector = highestResolution(),
override val previewResolution: ResolutionSelector = highestResolution()
) : Configuration {
/**
* Builder for [CameraConfiguration].
*/
class Builder internal constructor() {
private var cameraConfiguration: CameraConfiguration = default()
fun flash(selector: FlashSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
flashMode = selector
)
}
fun focusMode(selector: FocusModeSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
focusMode = selector
)
}
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
previewFpsRange = selector
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
)
}
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
cameraConfiguration.copy(
antiBandingMode = selector
)
}
fun jpegQuality(selector: QualitySelector): Builder = apply {
cameraConfiguration.copy(
jpegQuality = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
previewResolution = selector
)
}
fun photoResolution(selector: ResolutionSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
pictureResolution = selector
)
}
fun frameProcessor(frameProcessor: FrameProcessorJava): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
frameProcessor = frameProcessor::process
)
}
/**
* Builds a new [CameraConfiguration].
*/
fun build(): CameraConfiguration = cameraConfiguration
}
companion object {
/**
* Alias for [CameraConfiguration.default]
*/
@JvmStatic
fun standard(): CameraConfiguration = default()
/**
* Default [CameraConfiguration].
*/
@JvmStatic
fun default(): CameraConfiguration = CameraConfiguration()
/**
* Creates a new [CameraConfiguration.Builder].
*/
@JvmStatic
fun builder(): Builder = Builder()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
interface Configuration {
val flashMode: FlashSelector?
val focusMode: FocusModeSelector?
val jpegQuality: QualitySelector?
val frameProcessor: FrameProcessor?
val previewFpsRange: FpsRangeSelector?
val antiBandingMode: AntiBandingModeSelector?
val sensorSensitivity: SensorSensitivitySelector?
val previewResolution: ResolutionSelector?
val pictureResolution: ResolutionSelector?
}
@@ -0,0 +1,96 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
/**
* A camera update configuration.
*/
data class UpdateConfiguration(
override val flashMode: FlashSelector? = null,
override val focusMode: FocusModeSelector? = null,
override val jpegQuality: QualitySelector? = null,
override val frameProcessor: FrameProcessor? = null,
override val previewFpsRange: FpsRangeSelector? = null,
override val antiBandingMode: AntiBandingModeSelector? = null,
override val sensorSensitivity: SensorSensitivitySelector? = null,
override val previewResolution: ResolutionSelector? = null,
override val pictureResolution: ResolutionSelector? = null
) : Configuration {
/**
* Builder for [UpdateConfiguration].
*/
class Builder internal constructor() {
private var configuration = UpdateConfiguration()
fun flash(selector: FlashSelector): Builder = apply {
configuration = configuration.copy(
flashMode = selector
)
}
fun focusMode(selector: FocusModeSelector): Builder = apply {
configuration = configuration.copy(
focusMode = selector
)
}
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
configuration = configuration.copy(
previewFpsRange = selector
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
configuration = configuration.copy(
sensorSensitivity = selector
)
}
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
configuration = configuration.copy(
antiBandingMode = selector
)
}
fun jpegQuality(selector: QualitySelector): Builder = apply {
configuration = configuration.copy(
jpegQuality = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
configuration = configuration.copy(
previewResolution = selector
)
}
fun photoResolution(selector: ResolutionSelector): Builder = apply {
configuration = configuration.copy(
pictureResolution = selector
)
}
fun frameProcessor(frameProcessor: FrameProcessor): Builder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
}
/**
* Builds a new [UpdateConfiguration].
*/
fun build(): UpdateConfiguration = configuration
}
companion object {
/**
* Creates a new [UpdateConfiguration.Builder].
*/
@JvmStatic
fun builder(): Builder = Builder()
}
}
@@ -0,0 +1,33 @@
package io.fotoapparat.coroutines
import kotlinx.coroutines.experimental.CompletableDeferred
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.channels.BroadcastChannel
import kotlinx.coroutines.experimental.channels.ConflatedBroadcastChannel
/**
* A [ConflatedBroadcastChannel] which exposes a [getValue] which will [await] for at least one value.
*/
internal class AwaitBroadcastChannel<T>(
private val channel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel(),
private val deferred: CompletableDeferred<Boolean> = CompletableDeferred()
) : BroadcastChannel<T> by channel, Deferred<Boolean> by deferred {
/**
* The most recently sent element to this channel.
*/
suspend fun getValue(): T {
deferred.await()
return channel.value
}
override fun offer(element: T): Boolean {
deferred.complete(true)
return channel.offer(element)
}
override suspend fun send(element: T) {
deferred.complete(true)
channel.send(element)
}
}
@@ -1,36 +0,0 @@
package io.fotoapparat.error;
import android.os.Handler;
import android.os.Looper;
import io.fotoapparat.hardware.CameraException;
/**
* Factory methods for callbacks.
*/
public class Callbacks {
private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
/**
* @return CameraErrorCallback which will always move execution to the main thread.
*/
public static CameraErrorCallback onMainThread(final CameraErrorCallback original) {
return new CameraErrorCallback() {
@Override
public void onError(final CameraException e) {
if (Looper.myLooper() == Looper.getMainLooper()) {
original.onError(e);
} else {
MAIN_THREAD_HANDLER.post(new Runnable() {
@Override
public void run() {
original.onError(e);
}
});
}
}
};
}
}
@@ -1,27 +0,0 @@
package io.fotoapparat.error;
import io.fotoapparat.hardware.CameraException;
/**
* Notified when an camera error happens within Fotoapparat.
* <p>
* This method is always called from the main thread.
*/
public interface CameraErrorCallback {
/**
* No-op implementation of {@link CameraErrorCallback}.
*/
CameraErrorCallback NULL = new CameraErrorCallback() {
@Override
public void onError(CameraException e) {
// Do nothing
}
};
/**
* Notified when a camera error happens within Fotoapparat.
*/
void onError(CameraException e);
}
@@ -0,0 +1,16 @@
package io.fotoapparat.error
import io.fotoapparat.exception.camera.CameraException
/**
* Notified when an camera error happens within Fotoapparat.
*
* This method is always called from the main thread.
*/
interface CameraErrorListener {
/**
* Notified when a camera error happens within Fotoapparat.
*/
fun onError(e: CameraException)
}
@@ -0,0 +1,19 @@
package io.fotoapparat.error
import android.os.Looper
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.executeMainThread
typealias CameraErrorCallback = (CameraException) -> Unit
/**
* @return CameraErrorCallback which will always move execution to the main thread.
*/
fun CameraErrorCallback.onMainThread(): CameraErrorCallback = { 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,9 @@
package io.fotoapparat.exception.camera
/**
* Thrown when the preview surface didn't become available.
*/
class UnavailableSurfaceException : CameraException(
"No preview surface became available before CameraView got detached from window. " +
"Camera didn't start. You may ignore this exception."
)
@@ -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,18 @@
package io.fotoapparat.exif
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, rotationDegrees: Int)
}
@@ -0,0 +1,39 @@
package io.fotoapparat.exif
import android.media.ExifInterface
import io.fotoapparat.exception.FileSaveException
import java.io.File
import java.io.IOException
/**
* Writes Exif attributes.
*/
internal object ExifWriter : ExifOrientationWriter {
@Throws(FileSaveException::class)
override fun writeExifOrientation(file: File, rotationDegrees: Int) {
try {
ExifInterface(file.path).apply {
setAttribute(
ExifInterface.TAG_ORIENTATION,
toExifOrientation(rotationDegrees).toString()
)
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,83 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.FloatRange;
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.operators.ZoomOperator;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.lens.FocusResult;
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;
/**
* Abstraction for camera hardware.
*/
public interface CameraDevice extends CaptureOperator,
PreviewOperator, CapabilitiesOperator, OrientationOperator, ParametersOperator,
ConnectionOperator, SurfaceOperator, PreviewStreamOperator, RendererParametersOperator,
ExposureMeasurementOperator, AutoFocusOperator, AvailableLensPositionsProvider,
ZoomOperator {
@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();
@Override
void setZoom(@FloatRange(from = 0f, to = 1f) float level);
Parameters getCurrentParameters();
}
@@ -0,0 +1,419 @@
@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.toCameraId
import io.fotoapparat.coroutines.AwaitBroadcastChannel
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.convert.toFocusAreas
import io.fotoapparat.hardware.orientation.*
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.FocusMode
import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.apply.applyNewParameters
import io.fotoapparat.parameter.camera.convert.toCode
import io.fotoapparat.preview.PreviewStream
import io.fotoapparat.result.FocusResult
import io.fotoapparat.result.Photo
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.view.Preview
import kotlinx.coroutines.experimental.CompletableDeferred
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 val capabilities = CompletableDeferred<Capabilities>()
private val cameraParameters = AwaitBroadcastChannel<CameraParameters>()
private lateinit var previewStream: PreviewStream
private lateinit var surface: Surface
private lateinit var camera: Camera
private var cachedZoomParameters: Camera.Parameters? = null
private var displayOrientation: Orientation = Portrait
private var imageOrientation: Orientation = Portrait
private var previewOrientation: Orientation = Portrait
/**
* 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.complete(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(imageOrientation.degrees)
}
/**
* Returns the [Capabilities] of the camera.
*/
open suspend fun getCapabilities(): Capabilities {
logger.recordMethod()
return capabilities.await()
}
/**
* Returns the [CameraParameters] used.
*/
open suspend fun getParameters(): CameraParameters {
logger.recordMethod()
return cameraParameters.getValue()
}
/**
* Updates the desired camera parameters.
*/
open suspend fun updateParameters(cameraParameters: CameraParameters) {
logger.recordMethod()
this.cameraParameters.send(cameraParameters)
logger.log("New camera parameters are: $cameraParameters")
camera.updateParameters(cameraParameters)
}
/**
* Updates the frame processor.
*/
open fun updateFrameProcessor(frameProcessor: FrameProcessor?) {
logger.recordMethod()
previewStream.updateProcessorSafely(frameProcessor)
}
/**
* Sets the current orientation of the display.
*/
open fun setDisplayOrientation(orientationState: OrientationState) {
logger.recordMethod()
imageOrientation = computeImageOrientation(
deviceOrientation = orientationState.deviceOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
displayOrientation = computeDisplayOrientation(
screenOrientation = orientationState.screenOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
previewOrientation = computePreviewOrientation(
screenOrientation = orientationState.screenOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
logger.log("Image orientation is: $imageOrientation. " + lineSeparator +
"Display orientation is: $displayOrientation. " + lineSeparator +
"Preview orientation is: $previewOrientation."
)
previewStream.frameOrientation = previewOrientation
camera.setDisplayOrientation(displayOrientation.degrees)
}
/**
* 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 point where the focus & exposure metering will happen.
*/
open suspend fun setFocalPoint(focalRequest: FocalRequest) {
logger.recordMethod()
if (capabilities.await().canSetFocusingAreas()) {
camera.updateFocusingAreas(focalRequest)
}
}
/**
* Clears the point where the focus & exposure will happen.
*/
open fun clearFocalPoint() {
logger.recordMethod()
camera.clearFocusingAreas()
}
/**
* Sets the desired surface on which the camera's preview will be displayed.
*/
@Throws(IOException::class)
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(previewOrientation)
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 suspend fun Camera.updateFocusingAreas(focalRequest: FocalRequest) {
val focusingAreas = focalRequest.toFocusAreas(
displayOrientationDegrees = displayOrientation.degrees,
cameraIsMirrored = characteristics.isMirrored
)
parameters = parameters.apply {
with(capabilities.await()) {
if (maxMeteringAreas > 0) {
meteringAreas = focusingAreas
}
if (maxFocusAreas > 0) {
if (focusModes.contains(FocusMode.Auto)) {
focusMode = FocusMode.Auto.toCode()
}
focusAreas = focusingAreas
}
}
}
}
private fun Camera.clearFocusingAreas() {
parameters = parameters.apply {
with(capabilities) {
meteringAreas = null
focusAreas = null
}
}
}
}
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 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(this::setPreviewTexture)
.let(::Surface)
is Preview.Surface -> preview.surfaceHolder
.also(this::setPreviewDisplay)
.surface
}
private fun Camera.getPreviewResolution(previewOrientation: Orientation): Resolution {
return parameters.previewSize
.run {
PreviewSize(width, height)
}
.run {
when (previewOrientation) {
is Orientation.Vertical -> this
is Orientation.Horizontal -> flipDimensions()
}
}
}
private fun Capabilities.canSetFocusingAreas(): Boolean =
maxMeteringAreas > 0 || maxFocusAreas > 0
@@ -1,20 +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);
}
public CameraException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,155 +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;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.range.Ranges;
/**
* 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;
@NonNull
private final Set<Range<Integer>> previewFpsRanges;
@NonNull
private final Range<Integer> sensorSensitivityRange;
private final boolean zoomSupported;
public Capabilities(@NonNull Set<Size> photoSizes,
@NonNull Set<Size> previewSizes,
@NonNull Set<FocusMode> focusModes,
@NonNull Set<Flash> flashModes,
@NonNull Set<Range<Integer>> previewFpsRanges,
@NonNull Range<Integer> sensorSensitivityRange,
boolean zoomSupported) {
this.photoSizes = photoSizes;
this.previewSizes = previewSizes;
this.focusModes = focusModes;
this.flashModes = flashModes;
this.previewFpsRanges = previewFpsRanges;
this.sensorSensitivityRange = sensorSensitivityRange;
this.zoomSupported = zoomSupported;
}
/**
* @return Empty {@link Capabilities}.
*/
public static Capabilities empty() {
return new Capabilities(
Collections.<Size>emptySet(),
Collections.<Size>emptySet(),
Collections.<FocusMode>emptySet(),
Collections.<Flash>emptySet(),
Collections.<Range<Integer>>emptySet(),
Ranges.<Integer>emptyRange(),
false
);
}
/**
* @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;
}
/**
* @return list of supported preview fps ranges.
*/
public Set<Range<Integer>> supportedPreviewFpsRanges() {
return previewFpsRanges;
}
/**
* @return supported range of the sensor's sensitivity.
*/
public Range<Integer> supportedSensorSensitivityRange() {
return sensorSensitivityRange;
}
/**
* @return {@code true} if zoom feature is supported. {@code false} if it is not supported.
*/
public boolean isZoomSupported() {
return zoomSupported;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Capabilities)) return false;
Capabilities that = (Capabilities) o;
return zoomSupported == that.zoomSupported
&& photoSizes.equals(that.photoSizes)
&& previewSizes.equals(that.previewSizes)
&& focusModes.equals(that.focusModes)
&& flashModes.equals(that.flashModes)
&& previewFpsRanges.equals(that.previewFpsRanges)
&& sensorSensitivityRange.equals(that.sensorSensitivityRange);
}
@Override
public int hashCode() {
int result = photoSizes.hashCode();
result = 31 * result + previewSizes.hashCode();
result = 31 * result + focusModes.hashCode();
result = 31 * result + flashModes.hashCode();
result = 31 * result + previewFpsRanges.hashCode();
result = 31 * result + sensorSensitivityRange.hashCode();
result = 31 * result + (isZoomSupported() ? 1 : 0);
return result;
}
@Override
public String toString() {
return "Capabilities{" +
"photoSizes=" + photoSizes +
", previewSizes=" + previewSizes +
", focusModes=" + focusModes +
", flashModes=" + flashModes +
", previewFpsRanges=" + previewFpsRanges +
", supportedSensorSensitivityRange=" + sensorSensitivityRange +
", zoomSupported=" + zoomSupported +
'}';
}
}
@@ -0,0 +1,192 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import io.fotoapparat.characteristic.getCharacteristics
import io.fotoapparat.concurrent.CameraExecutor
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.hardware.orientation.Orientation
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.selector.LensPositionSelector
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.FocalPointSelector
import kotlinx.coroutines.experimental.CompletableDeferred
/**
* Phone.
*/
internal open class Device(
internal open val logger: Logger,
private val display: Display,
internal open val scaleType: ScaleType,
internal open val cameraRenderer: CameraRenderer,
internal val focusPointSelector: FocalPointSelector?,
internal val executor: CameraExecutor,
numberOfCameras: Int = Camera.getNumberOfCameras(),
initialConfiguration: CameraConfiguration, initialLensPositionSelector: LensPositionSelector
) {
private val cameras = (0 until numberOfCameras).map { cameraId ->
CameraDevice(
logger = logger,
characteristics = getCharacteristics(cameraId)
)
}
private var lensPositionSelector: LensPositionSelector = initialLensPositionSelector
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
private var savedConfiguration = CameraConfiguration.default()
init {
updateLensPositionSelector(initialLensPositionSelector)
savedConfiguration = initialConfiguration
}
/**
* Selects a camera.
*/
open fun canSelectCamera(lensPositionSelector: LensPositionSelector): 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)
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
}
/**
* Clears the selected camera.
*/
open fun clearSelectedCamera() {
selectedCameraDevice = CompletableDeferred()
}
/**
* Waits and returns the selected camera.
*/
open suspend fun awaitSelectedCamera(): CameraDevice = 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 = 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 Orientation of the screen.
*/
open fun getScreenOrientation(): Orientation {
return display.getOrientation()
}
/**
* Updates the desired from the user camera lens position.
*/
open fun updateLensPositionSelector(newLensPosition: LensPositionSelector) {
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 = savedConfiguration
/**
* @return The selected [CameraParameters] for the given [CameraDevice].
*/
open suspend fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters =
getCameraParameters(
cameraConfiguration = savedConfiguration,
capabilities = cameraDevice.getCapabilities()
)
/**
* @return The frame processor.
*/
open fun getFrameProcessor(): FrameProcessor = savedConfiguration.frameProcessor
/**
* @return The desired from the user camera lens position.
*/
open fun getLensPositionSelector(): LensPositionSelector = 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: LensPositionSelector
): CameraDevice? {
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
val desiredPosition = lensPositionSelector(lensPositions)
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
}
@@ -0,0 +1,28 @@
package io.fotoapparat.hardware
import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executors
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()
/**
* 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,32 @@
package io.fotoapparat.hardware.display
import android.content.Context
import android.view.Surface
import android.view.WindowManager
import io.fotoapparat.hardware.orientation.Orientation
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.ReverseLandscape
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
import io.fotoapparat.hardware.orientation.Orientation.Vertical.ReversePortrait
/**
* A phone's display.
*/
internal open class Display(context: Context) {
private val display = context.getDisplay()
/**
* Returns the orientation of the screen.
*/
open fun getOrientation(): Orientation = when (display.rotation) {
Surface.ROTATION_0 -> Portrait
Surface.ROTATION_90 -> Landscape
Surface.ROTATION_180 -> ReversePortrait
Surface.ROTATION_270 -> ReverseLandscape
else -> Portrait
}
}
private fun Context.getDisplay() = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
@@ -0,0 +1,20 @@
package io.fotoapparat.hardware.metering
import io.fotoapparat.parameter.Resolution
/**
* The request to focus camera at a particular point.
*/
data class FocalRequest(
/**
* The point where when user would like to focus.
*/
val point: PointF,
/**
* Resolution of the preview
*/
val previewResolution: Resolution
)
@@ -0,0 +1,10 @@
package io.fotoapparat.hardware.metering
/**
* A point in arbitrary scale.
*/
data class PointF(
val x: Float,
val y: Float
)
@@ -0,0 +1,117 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware.metering.convert
import android.graphics.Matrix
import android.graphics.Rect
import android.hardware.Camera
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.PointF
import io.fotoapparat.parameter.Resolution
/**
* The weight of the camera area.
* Only 1will be used, so max weight, `1000`, is used.
*/
private const val WEIGHT = 1000
/**
* From -1000 to 1000
*
* @see Camera.Area
*/
private const val CAMERA_BOUNDS_RANGE = 2000f
/**
* The half dimension size of a focus area.
*
* The camera will focus on a maximum area of `(2 * this) ^ 2`.
*/
private const val FOCUS_AREA_HALF_SIZE = 50
/**
* Converts a [FocalRequest] to a list of [Camera.Area] that the camera should focus at.
*/
internal fun FocalRequest.toFocusAreas(
displayOrientationDegrees: Int,
cameraIsMirrored: Boolean
): List<Camera.Area> = listOf(Camera.Area(
focusBounds(
displayOrientationDegrees = displayOrientationDegrees.toFloat(),
cameraIsMirrored = cameraIsMirrored
),
WEIGHT
))
private fun FocalRequest.focusBounds(
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
): Rect = point
.adjustPointToCameraPreview(
previewResolution,
displayOrientationDegrees,
cameraIsMirrored
)
.toBoundsRect()
private fun PointF.adjustPointToCameraPreview(
visibleResolution: Resolution,
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
): PointF = Matrix()
.configMatrix(
visibleResolution,
displayOrientationDegrees,
cameraIsMirrored
)
.run {
floatArrayOf(x, y).also {
mapPoints(it)
}
}
.let {
PointF(
it[0].verifyInBounds(),
it[1].verifyInBounds()
)
}
/**
* The point value should mainly be adjusted by `2000 * value - 1000`
* in order to be in `-1000..1000` range.
*/
private fun Matrix.configMatrix(
visibleResolution: Resolution,
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
) = apply {
postScale(
CAMERA_BOUNDS_RANGE / visibleResolution.width.toFloat(),
CAMERA_BOUNDS_RANGE / visibleResolution.height.toFloat()
)
postTranslate(
-CAMERA_BOUNDS_RANGE / 2,
-CAMERA_BOUNDS_RANGE / 2
)
postRotate(-displayOrientationDegrees)
postScale(
if (cameraIsMirrored) -1f else 1f,
1f
)
}
private fun Float.verifyInBounds(): Float {
return takeIf { it in -1000f..1000f }
?: throw IllegalArgumentException("Point value should be between -1000.0 and 1000.0. Was $this")
}
private fun PointF.toBoundsRect() = Rect(
(x - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(y - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(x + FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(y + FOCUS_AREA_HALF_SIZE).ensureAreaBound()
)
private fun Float.ensureAreaBound() = clamp(-1000f, 1000f).toInt()
private fun Float.clamp(min: Float, max: Float): Float = Math.max(min, Math.min(this, max))
@@ -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);
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import android.support.annotation.FloatRange;
/**
* Modifies zoom level of the camera.
*/
public interface ZoomOperator {
/**
* Changes zoom level of the camera. Must be called only if zoom is supported.
*
* @param level normalized zoom level. Value in range [0..1].
*/
void setZoom(@FloatRange(from = 0f, to = 1f) float level);
}
@@ -0,0 +1,56 @@
package io.fotoapparat.hardware.orientation
typealias DeviceOrientation = Orientation
typealias ScreenOrientation = Orientation
/**
* The device orientation.
*/
sealed class Orientation(
val degrees: Int
) {
/**
* A vertical device orientation.
*/
sealed class Vertical(degrees: Int) : Orientation(degrees) {
/**
* A vertical, normal orientation.
*/
object Portrait : Vertical(0)
/**
* A reversed (flipped phone) orientation.
*/
object ReversePortrait : Vertical(180)
}
/**
* A horizontal device orientation.
*/
sealed class Horizontal(degrees: Int) : Orientation(degrees) {
/**
* A 90 degrees clockwise from "normal", orientation.
*/
object Landscape : Horizontal(90)
/**
* A 90 degrees counter-clockwise from "normal", orientation.
*/
object ReverseLandscape : Horizontal(270)
}
}
internal fun Int.toOrientation(): Orientation {
return when (this) {
0, 360 -> Orientation.Vertical.Portrait
90 -> Orientation.Horizontal.Landscape
180 -> Orientation.Vertical.ReversePortrait
270 -> Orientation.Horizontal.ReverseLandscape
else -> throw IllegalArgumentException("Cannot convert $this to absolute Orientation.")
}
}
@@ -0,0 +1,75 @@
package io.fotoapparat.hardware.orientation
/**
* @param screenOrientation Orientation of the display.
* @param cameraOrientation Orientation of the camera sensor.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case
* for front cameras). `false` if it is not mirrored.
*
* @return rotation of the image relatively to current device orientation.
*/
fun computePreviewOrientation(
screenOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val mirroredCameraModifier = if (cameraIsMirrored) -1 else 1
val rotation = (720
+ mirroredCameraModifier * screenOrientation.degrees
- cameraOrientation.degrees
) % 360
return rotation.toOrientation()
}
/**
* @param deviceOrientation Orientation of the device.
* @param cameraOrientation Orientation of the camera sensor.
* @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(
deviceOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val screenOrientationDegrees = deviceOrientation.degrees
val cameraRotationDegrees = cameraOrientation.degrees
val rotation = if (cameraIsMirrored) {
360 - (cameraRotationDegrees - screenOrientationDegrees + 360) % 360
} else {
360 - (cameraRotationDegrees + screenOrientationDegrees) % 360
}
return rotation.toOrientation()
}
/**
* @param screenOrientation Orientation of the display.
* @param cameraOrientation Orientation of the camera sensor.
* @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(
screenOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val screenOrientationDegrees = screenOrientation.degrees
val cameraRotationDegrees = cameraOrientation.degrees
val rotation = if (cameraIsMirrored) {
(360 - (cameraRotationDegrees + screenOrientationDegrees) % 360) % 360
} else {
(cameraRotationDegrees - screenOrientationDegrees + 360) % 360
}
return rotation.toOrientation()
}
@@ -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,61 @@
package io.fotoapparat.hardware.orientation
import android.content.Context
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
/**
* Monitors orientation of the device.
*/
internal open class OrientationSensor(
private val rotationListener: RotationListener,
private val device: Device
) {
private val onOrientationChanged: (DeviceRotationDegrees) -> Unit = { deviceRotation ->
deviceRotation.toClosestRightAngle()
.toOrientation()
.takeIf { it != lastKnownDeviceOrientation }
?.let {
val state = OrientationState(
deviceOrientation = it,
screenOrientation = device.getScreenOrientation()
)
lastKnownDeviceOrientation = state.deviceOrientation
listener(state)
}
}
private lateinit var listener: (OrientationState) -> Unit
private var lastKnownDeviceOrientation: Orientation = Portrait
constructor(
context: Context,
device: Device
) : this(
RotationListener(context),
device
)
init {
rotationListener.orientationChanged = onOrientationChanged
}
/**
* Starts monitoring device's orientation state.
*/
open fun start(listener: (OrientationState) -> Unit) {
this.listener = listener
rotationListener.enable()
}
/**
* Stops monitoring device's orientation.
*/
open fun stop() {
rotationListener.disable()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.hardware.orientation
/**
* Phone orientation states.
*/
data class OrientationState(
/**
* The current orientation the device is being hold.
*/
val deviceOrientation: DeviceOrientation,
/**
* The current orientation of the screen.
*/
val screenOrientation: ScreenOrientation
)
@@ -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;
}
}
@@ -0,0 +1,14 @@
package io.fotoapparat.hardware.orientation
typealias DeviceRotationDegrees = Int
/**
* @return closest right angle to given value. That is: 0, 90, 180, 270.
*/
internal fun Int.toClosestRightAngle(): Int {
val roundUp = this % 90 > 45
val roundAppModifier = if (roundUp) 1 else 0
return (this / 90 + roundAppModifier) * 90 % 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.
*/
internal open class RotationListener(
context: Context
) : OrientationEventListener(context) {
lateinit var orientationChanged: (DeviceRotationDegrees) -> Unit
override fun onOrientationChanged(orientation: DeviceRotationDegrees) {
if (canDetectOrientation()) {
orientationChanged(orientation)
}
}
}
@@ -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);
}
}

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