Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf22a9a1f8 | |||
| 6781e349f9 | |||
| 08446ff533 | |||
| 9a45b8f400 | |||
| e8092902d2 | |||
| d679ff7454 | |||
| 4583b8d992 | |||
| 3da2615acd | |||
| 12c3c3a852 | |||
| 0458642f75 | |||
| cd6cebdb6c | |||
| 2513e42830 | |||
| 4ed72421aa | |||
| dddd81600a | |||
| 2c80a903c1 | |||
| 3f6ecbd37e | |||
| d9114d4824 | |||
| 1768d73e59 | |||
| 51aa9eb9dc | |||
| 1cfbd2216f | |||
| bde4c27f8b | |||
| f4b60a445a | |||
| 64186ec825 | |||
| 96f9add77d | |||
| b6a6f56e38 | |||
| a694830c1c | |||
| 352a96a13c | |||
| 33c5054150 | |||
| ba02497fbd | |||
| 95196e046b | |||
| cb00abc810 | |||
| 5e9a6afff7 | |||
| 75d20c2f37 | |||
| d0da61bfb8 | |||
| 92f099500d | |||
| 15b4f35555 | |||
| c1b293da58 | |||
| 09cca52111 | |||
| 9d6f4cb9e8 | |||
| 2ea813a257 | |||
| 8dbe53d261 | |||
| 4bd05b679f | |||
| 01dc60df24 | |||
| 2b05601405 | |||
| 1ca2462f07 | |||
| e553699335 | |||
| 1d6d2d0977 | |||
| 94268a339a | |||
| 03170f6b26 | |||
| fe719e8bf3 | |||
| d7db6fa196 | |||
| 29cc23e047 | |||
| 89a172dbb1 | |||
| e4acda6aea | |||
| 3fc55404df | |||
| 16fb6bd94c | |||
| df59b3996e | |||
| b8e43e836a | |||
| 6e15327f6c | |||
| 6aef37b3f8 | |||
| 21e4b89bf7 | |||
| e3f0aa7421 | |||
| 35062675d9 | |||
| aff03c17c8 | |||
| b4e6db0a04 | |||
| 7517702165 | |||
| 56d32eee60 | |||
| 132736e4f7 | |||
| 2ac387d6ff | |||
| f4ff8b91b9 | |||
| 7b047501a6 | |||
| 80f43fcf63 | |||
| b7417650c6 | |||
| e7e264f522 | |||
| e501d0d25f | |||
| 39765cdb07 | |||
| 71480f4fa8 | |||
| a6028d56f3 | |||
| 086a2ba3d3 | |||
| db642767d2 | |||
| a698a5bf50 | |||
| a06edaebdc | |||
| 18e8624779 | |||
| 37f53ed6cb | |||
| 27ca565b3e | |||
| 7fbf4c1e5a | |||
| 9d45e0ff89 | |||
| 68bc88e9b7 | |||
| 86c1a7558c | |||
| 0708c795c7 | |||
| b4a6df6299 | |||
| 143a3ad47c | |||
| 41bec5dc0a | |||
| ba69cce007 | |||
| 65c67ca802 | |||
| 4e2c4a21fe | |||
| f151194798 | |||
| c7dd5b0d0a | |||
| 21652d836e | |||
| 8e55a210c6 | |||
| 88d468b539 | |||
| 672f0d9c45 | |||
| db16b73ce8 | |||
| e506312235 | |||
| 036d14995e | |||
| a835bb7534 | |||
| 58007469fa | |||
| 668511f8b5 | |||
| 2a72b24f89 | |||
| ec9a81994f | |||
| 8b69a5c9ab | |||
| d082b60f8b | |||
| 87659051df | |||
| 7fc912513a | |||
| ff1eb975a0 | |||
| 110fda0a0a | |||
| fe36a7d05b | |||
| c354e323b3 | |||
| 2f2b7a0d0f | |||
| 1348705a28 | |||
| d1991c0dc1 | |||
| 25b228c211 | |||
| d3391f48db | |||
| 627d03e2d0 | |||
| a89c22c339 | |||
| c0bf07a78d | |||
| a8614f0e9e | |||
| e3b4ee91f0 | |||
| 30a3f132cc | |||
| a4fe1bd98d | |||
| 83726252f3 | |||
| 644f7dab27 | |||
| 2aa8fd620c | |||
| 5c622714e5 | |||
| 688b47517f | |||
| 0687c54c5e | |||
| 051fb3a157 | |||
| feaa19ec2b | |||
| 148ede7245 | |||
| 2a6b4f29d6 | |||
| c120a42aa5 | |||
| cac8e18494 | |||
| 1d575af7ab | |||
| 4370ff3772 | |||
| 330dc6e9e5 | |||
| 07c0322432 | |||
| e4b01725e4 | |||
| 3b8195bbe7 | |||
| 1336714343 | |||
| c0bd25ce58 | |||
| df1079c233 |
@@ -0,0 +1,35 @@
|
||||
|
||||
Please check if your issue exists.
|
||||
|
||||
Issues should only be posted after you have been able to reproduce them and confirm that they are either a missing functionality or a bug.
|
||||
|
||||
#### What are you trying to achieve or the steps to reproduce?
|
||||
|
||||
Describe your issue here, include as much detail as neccessary to reproduce the issue
|
||||
or implement a missing functionality.
|
||||
|
||||
```java
|
||||
// Wrap code in markdown source tags
|
||||
```
|
||||
|
||||
|
||||
#### How did you initialize FA?
|
||||
|
||||
```java
|
||||
// Wrap code in markdown source tags
|
||||
```
|
||||
|
||||
|
||||
#### What was the result you received?
|
||||
|
||||
|
||||
|
||||
#### What did you expect?
|
||||
|
||||
|
||||
|
||||
#### Context:
|
||||
|
||||
* *FA version*:
|
||||
* *Devices/APIs affected*:
|
||||
* *any other relevant information*:
|
||||
+6
-2
@@ -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
|
||||
|
||||
@@ -8,24 +8,27 @@
|
||||
Camera API in Android is hard. Having 2 different API for new and old Camera does not make things any easier. But fret not, that is your lucky day! After several years of working with Camera we came up with Fotoapparat.
|
||||
|
||||
What it provides:
|
||||
- Simple, yet powerful API for working with Camera.
|
||||
- Support of Camera1 as well as Camera2.
|
||||
- Camera API which does not allow you to shoot yourself in the foot.
|
||||
- Simple yet powerful parameters customization.
|
||||
- Standalone custom `CameraView` which can be integrated into any `Activity`.
|
||||
- Fixes and workarounds for device-specific problems.
|
||||
- Both Kotlin and Java friendly configurations.
|
||||
- Last, but not least, non 0% test coverage.
|
||||
|
||||
|
||||
Taking picture becomes as simple as:
|
||||
|
||||
```java
|
||||
Fotoapparat fotoapparat = Fotoapparat
|
||||
.with(context)
|
||||
.into(cameraView)
|
||||
.build();
|
||||
|
||||
fotoapparat.start();
|
||||
```kotlin
|
||||
val fotoapparat = Fotoapparat(
|
||||
context = this,
|
||||
view = cameraView
|
||||
)
|
||||
|
||||
fotoapparat.start()
|
||||
|
||||
fotoapparat
|
||||
.takePicture()
|
||||
.saveToFile(someFile);
|
||||
.saveToFile(someFile)
|
||||
```
|
||||
|
||||
## How it works
|
||||
@@ -43,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()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -92,50 +87,73 @@ 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 cameras, `Fotoapparat.switchTo()` can be used with the new desired `lensPosition` and its `cameraConfiguration`.
|
||||
|
||||
```kotlin
|
||||
fotoapparat.switchTo(
|
||||
lensPosition = front(),
|
||||
cameraConfiguration = newConfigurationForFrontCamera
|
||||
)
|
||||
```
|
||||
|
||||
@@ -148,7 +166,7 @@ repositories {
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
compile 'io.fotoapparat.fotoapparat:library:1.3.0'
|
||||
compile 'io.fotoapparat.fotoapparat:library:2.1.0'
|
||||
```
|
||||
|
||||
Camera permission will be automatically added to your `AndroidManifest.xml`. Do not forget to request this permission on Marshmallow and higher.
|
||||
|
||||
+32
-2
@@ -1,17 +1,47 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
versions = [
|
||||
gradle : '4.4.1',
|
||||
kotlin : '1.2.10',
|
||||
code : 1,
|
||||
name : '1.0.0',
|
||||
sdk : [
|
||||
minimum: 16,
|
||||
target : 27
|
||||
],
|
||||
android: [
|
||||
buildTools: '27.0.3',
|
||||
support : '27.0.2'
|
||||
],
|
||||
rx : [
|
||||
rxJava1: '1.2.9',
|
||||
rxJava2: '2.0.8'
|
||||
],
|
||||
test : [
|
||||
junit : '4.12',
|
||||
mockito: '2.13.0'
|
||||
],
|
||||
util : [
|
||||
commons: '2.5'
|
||||
]
|
||||
]
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:${project.androidBuildToolsVersion}"
|
||||
classpath 'com.android.tools.build:gradle:3.1.0-alpha07'
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
@@ -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) }
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
@@ -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) }
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
@@ -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) }
|
||||
}
|
||||
-58
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ An `Adapter` for adapting RxJava 2.x types.
|
||||
Available types:
|
||||
|
||||
* `Observable<T>`
|
||||
* `Flowable<T>`
|
||||
* `Single<T>`
|
||||
* `Completable`
|
||||
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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) }
|
||||
}
|
||||
|
||||
+30
@@ -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) }
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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) }
|
||||
}
|
||||
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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) }
|
||||
}
|
||||
-58
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
+55
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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,316 +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.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.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 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,
|
||||
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.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,
|
||||
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
|
||||
);
|
||||
|
||||
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine = new ConfigurePreviewStreamRoutine(
|
||||
cameraDevice,
|
||||
builder.frameProcessor
|
||||
);
|
||||
|
||||
CapabilitiesProvider capabilitiesProvider = new CapabilitiesProvider(
|
||||
cameraDevice,
|
||||
SERIAL_EXECUTOR
|
||||
);
|
||||
|
||||
TakePictureRoutine takePictureRoutine = new TakePictureRoutine(
|
||||
cameraDevice,
|
||||
SERIAL_EXECUTOR
|
||||
);
|
||||
|
||||
AutoFocusRoutine autoFocusRoutine = new AutoFocusRoutine(
|
||||
cameraDevice,
|
||||
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,
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,250 @@
|
||||
package io.fotoapparat
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.FloatRange
|
||||
import io.fotoapparat.capability.Capabilities
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.configuration.Configuration
|
||||
import io.fotoapparat.error.onMainThread
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.hardware.Device
|
||||
import io.fotoapparat.hardware.display.Display
|
||||
import io.fotoapparat.hardware.execute
|
||||
import io.fotoapparat.hardware.executeTask
|
||||
import io.fotoapparat.hardware.orientation.OrientationSensor
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.log.none
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.result.*
|
||||
import io.fotoapparat.routine.camera.bootStart
|
||||
import io.fotoapparat.routine.camera.shutDown
|
||||
import io.fotoapparat.routine.camera.switchCamera
|
||||
import io.fotoapparat.routine.camera.updateDeviceConfiguration
|
||||
import io.fotoapparat.routine.capability.getCapabilities
|
||||
import io.fotoapparat.routine.focus.focus
|
||||
import io.fotoapparat.routine.parameter.getCurrentParameters
|
||||
import io.fotoapparat.routine.photo.takePhoto
|
||||
import io.fotoapparat.routine.zoom.updateZoomLevel
|
||||
import io.fotoapparat.selector.back
|
||||
import io.fotoapparat.selector.external
|
||||
import io.fotoapparat.selector.firstAvailable
|
||||
import io.fotoapparat.selector.front
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import io.fotoapparat.view.FocalPointSelector
|
||||
import java.util.concurrent.FutureTask
|
||||
|
||||
/**
|
||||
* Camera. Takes pictures.
|
||||
*/
|
||||
class Fotoapparat
|
||||
@JvmOverloads constructor(
|
||||
context: Context,
|
||||
view: CameraRenderer,
|
||||
focusView: FocalPointSelector? = null,
|
||||
lensPosition: Collection<LensPosition>.() -> LensPosition? = firstAvailable(
|
||||
back(),
|
||||
front(),
|
||||
external()
|
||||
),
|
||||
scaleType: ScaleType = ScaleType.CenterCrop,
|
||||
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
|
||||
cameraErrorCallback: ((CameraException) -> Unit) = {},
|
||||
private val logger: Logger = none()
|
||||
) {
|
||||
|
||||
private val mainThreadErrorCallback = cameraErrorCallback.onMainThread()
|
||||
|
||||
private val display = Display(context)
|
||||
|
||||
private val device = Device(
|
||||
cameraRenderer = view,
|
||||
focusPointSelector = focusView,
|
||||
logger = logger,
|
||||
display = display,
|
||||
scaleType = scaleType,
|
||||
initialLensPositionSelector = lensPosition,
|
||||
initialConfiguration = cameraConfiguration
|
||||
)
|
||||
|
||||
private val orientationSensor = OrientationSensor(
|
||||
context = context,
|
||||
device = device
|
||||
)
|
||||
|
||||
init {
|
||||
logger.recordMethod()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts camera.
|
||||
*
|
||||
* @throws IllegalStateException If the camera has already started.
|
||||
*/
|
||||
fun start() {
|
||||
logger.recordMethod()
|
||||
|
||||
execute {
|
||||
device.bootStart(
|
||||
orientationSensor = orientationSensor,
|
||||
mainThreadErrorCallback = mainThreadErrorCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops camera.
|
||||
*
|
||||
* @throws IllegalStateException If the camera has not started.
|
||||
*/
|
||||
fun stop() {
|
||||
logger.recordMethod()
|
||||
|
||||
execute {
|
||||
device.shutDown(
|
||||
orientationSensor = orientationSensor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes picture, returns immediately.
|
||||
*
|
||||
* @return [PhotoResult] which will deliver result asynchronously.
|
||||
*/
|
||||
fun takePicture(): PhotoResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val takePictureTask = FutureTask<Photo> {
|
||||
device.takePhoto()
|
||||
}
|
||||
|
||||
executeTask(takePictureTask)
|
||||
|
||||
return PhotoResult.fromFuture(takePictureTask, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides camera capabilities asynchronously, returns immediately.
|
||||
*
|
||||
* @return [CapabilitiesResult] which will deliver result asynchronously.
|
||||
*/
|
||||
fun getCapabilities(): CapabilitiesResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val getCapabilitiesTask = FutureTask<Capabilities> {
|
||||
device.getCapabilities()
|
||||
}
|
||||
|
||||
executeTask(getCapabilitiesTask)
|
||||
|
||||
return PendingResult.fromFuture(getCapabilitiesTask, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides current camera parameters asynchronously, returns immediately.
|
||||
*
|
||||
* @return [ParametersResult] which will deliver result asynchronously.
|
||||
*/
|
||||
fun getCurrentParameters(): ParametersResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val getCameraParametersTask = FutureTask<CameraParameters> {
|
||||
device.getCurrentParameters()
|
||||
}
|
||||
|
||||
executeTask(getCameraParametersTask)
|
||||
|
||||
return PendingResult.fromFuture(getCameraParametersTask, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current configuration.
|
||||
*
|
||||
* @throws IllegalStateException If the current camera has not started.
|
||||
*/
|
||||
fun updateConfiguration(newConfiguration: Configuration) = execute {
|
||||
logger.recordMethod()
|
||||
|
||||
device.updateDeviceConfiguration(newConfiguration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously updates zoom level of the camera.
|
||||
* If zoom is not supported by the device - does nothing.
|
||||
*
|
||||
* @param zoomLevel Zoom level of the camera. A value between 0 and 1.
|
||||
* @throws IllegalStateException If the current camera has not started.
|
||||
*/
|
||||
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = execute {
|
||||
logger.recordMethod()
|
||||
|
||||
device.updateZoomLevel(
|
||||
zoomLevel = zoomLevel
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs auto focus. If it is not available or not enabled, does nothing.
|
||||
*
|
||||
* @see Fotoapparat.focus
|
||||
*/
|
||||
fun autoFocus(): Fotoapparat {
|
||||
logger.recordMethod()
|
||||
focus()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to focus the camera asynchronously.
|
||||
*
|
||||
* @return the pending result of focus operation which will deliver result asynchronously.
|
||||
*
|
||||
* @see Fotoapparat.autoFocus
|
||||
*/
|
||||
fun focus(): PendingResult<FocusResult> {
|
||||
logger.recordMethod()
|
||||
|
||||
val focusTask = FutureTask<FocusResult> {
|
||||
device.focus()
|
||||
}
|
||||
executeTask(focusTask)
|
||||
|
||||
return PendingResult.fromFuture(focusTask, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to another camera. If previous camera has already started then it will be
|
||||
* stopped automatically and new will start.
|
||||
*/
|
||||
fun switchTo(
|
||||
lensPosition: Collection<LensPosition>.() -> LensPosition?,
|
||||
cameraConfiguration: CameraConfiguration
|
||||
) {
|
||||
logger.recordMethod()
|
||||
execute {
|
||||
device.switchCamera(
|
||||
newLensPositionSelector = lensPosition,
|
||||
newConfiguration = cameraConfiguration,
|
||||
mainThreadErrorCallback = mainThreadErrorCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if selected lens position is available. `false` if it is not available.
|
||||
*/
|
||||
fun isAvailable(
|
||||
selector: (Collection<LensPosition>) -> LensPosition?
|
||||
): Boolean {
|
||||
return device.canSelectCamera(selector)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun with(context: Context) = FotoapparatBuilder(context)
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package io.fotoapparat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import io.fotoapparat.error.CameraErrorCallback;
|
||||
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.selector.FlashSelectors;
|
||||
import io.fotoapparat.parameter.selector.SelectorFunction;
|
||||
import io.fotoapparat.preview.FrameProcessor;
|
||||
import io.fotoapparat.view.CameraRenderer;
|
||||
import io.fotoapparat.view.CameraView;
|
||||
|
||||
import static io.fotoapparat.hardware.provider.CameraProviders.v1;
|
||||
import static io.fotoapparat.parameter.selector.FocusModeSelectors.autoFocus;
|
||||
import static io.fotoapparat.parameter.selector.FocusModeSelectors.continuousFocus;
|
||||
import static io.fotoapparat.parameter.selector.FocusModeSelectors.fixed;
|
||||
import static io.fotoapparat.parameter.selector.LensPositionSelectors.back;
|
||||
import static io.fotoapparat.parameter.selector.LensPositionSelectors.external;
|
||||
import static io.fotoapparat.parameter.selector.LensPositionSelectors.front;
|
||||
import static io.fotoapparat.parameter.selector.Selectors.firstAvailable;
|
||||
import static io.fotoapparat.parameter.selector.SizeSelectors.biggestSize;
|
||||
|
||||
/**
|
||||
* Builder for {@link Fotoapparat}.
|
||||
*/
|
||||
public class FotoapparatBuilder {
|
||||
|
||||
Context context;
|
||||
CameraProvider cameraProvider = v1();
|
||||
CameraRenderer renderer;
|
||||
|
||||
SelectorFunction<LensPosition> lensPositionSelector = firstAvailable(
|
||||
back(),
|
||||
front(),
|
||||
external()
|
||||
);
|
||||
SelectorFunction<Size> photoSizeSelector = biggestSize();
|
||||
SelectorFunction<Size> previewSizeSelector = biggestSize();
|
||||
SelectorFunction<FocusMode> focusModeSelector = firstAvailable(
|
||||
continuousFocus(),
|
||||
autoFocus(),
|
||||
fixed()
|
||||
);
|
||||
SelectorFunction<Flash> flashSelector = FlashSelectors.off();
|
||||
|
||||
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<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<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<FocusMode> selector) {
|
||||
focusModeSelector = selector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects flash mode from list of available modes.
|
||||
*/
|
||||
public FotoapparatBuilder flash(@NonNull SelectorFunction<Flash> selector) {
|
||||
flashSelector = selector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector camera sensor position from list of available positions.
|
||||
*/
|
||||
public FotoapparatBuilder lensPosition(@NonNull SelectorFunction<LensPosition> selector) {
|
||||
lensPositionSelector = selector;
|
||||
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,186 @@
|
||||
package io.fotoapparat
|
||||
|
||||
import android.content.Context
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.log.none
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.selector.back
|
||||
import io.fotoapparat.selector.external
|
||||
import io.fotoapparat.selector.firstAvailable
|
||||
import io.fotoapparat.selector.front
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import io.fotoapparat.view.CameraView
|
||||
|
||||
/**
|
||||
* Builder for [Fotoapparat].
|
||||
*/
|
||||
class FotoapparatBuilder internal constructor(private var context: Context) {
|
||||
|
||||
internal var lensPositionSelector: Iterable<LensPosition>.() -> LensPosition? = firstAvailable(
|
||||
back(),
|
||||
front(),
|
||||
external()
|
||||
)
|
||||
internal var cameraErrorCallback: (CameraException) -> Unit = {}
|
||||
internal var renderer: CameraRenderer? = null
|
||||
internal var scaleType: ScaleType = ScaleType.CenterCrop
|
||||
internal var logger: Logger = none()
|
||||
|
||||
internal var configuration = CameraConfiguration.default()
|
||||
|
||||
/**
|
||||
* @param selector camera sensor position from list of available positions.
|
||||
*/
|
||||
fun lensPosition(selector: Iterable<LensPosition>.() -> LensPosition?): FotoapparatBuilder {
|
||||
lensPositionSelector = selector
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scaleType of preview inside the view.
|
||||
*/
|
||||
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder {
|
||||
this.scaleType = scaleType
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
|
||||
*/
|
||||
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
|
||||
*/
|
||||
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param selector selects focus mode from list of available modes.
|
||||
*/
|
||||
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects flash mode from list of available modes.
|
||||
*/
|
||||
fun flash(selector: Iterable<Flash>.() -> Flash?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects preview FPS range from list of available ranges.
|
||||
*/
|
||||
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects ISO value from range of available values.
|
||||
*/
|
||||
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector of the Jpeg picture quality.
|
||||
*/
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param frameProcessor receives preview frames for processing.
|
||||
* @see FrameProcessor
|
||||
*/
|
||||
fun frameProcessor(frameProcessor: (Frame) -> Unit): FotoapparatBuilder {
|
||||
configuration = configuration.copy(
|
||||
frameProcessor = frameProcessor
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param logger logger which will print logs. No logger is set by default.
|
||||
* @see io.fotoapparat.log.Loggers
|
||||
*/
|
||||
fun logger(logger: Logger): FotoapparatBuilder {
|
||||
this.logger = logger
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callback which will be notified when camera error happens in Fotoapparat.
|
||||
* @see CameraErrorCallback
|
||||
*/
|
||||
fun cameraErrorCallback(callback: (CameraException) -> Unit): FotoapparatBuilder {
|
||||
cameraErrorCallback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renderer view which will draw the stream from the camera.
|
||||
* @see CameraView
|
||||
*/
|
||||
fun into(renderer: CameraRenderer): FotoapparatBuilder {
|
||||
this.renderer = renderer
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @return set up instance of [Fotoapparat].
|
||||
* @throws IllegalStateException if some mandatory parameters are not specified.
|
||||
*/
|
||||
fun build() = buildInternal(
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
private fun buildInternal(
|
||||
renderer: CameraRenderer?
|
||||
): Fotoapparat {
|
||||
if (renderer == null) {
|
||||
throw IllegalStateException("CameraRenderer is mandatory.")
|
||||
}
|
||||
|
||||
return Fotoapparat(
|
||||
context = context,
|
||||
view = renderer,
|
||||
lensPosition = lensPositionSelector,
|
||||
cameraConfiguration = configuration,
|
||||
scaleType = scaleType,
|
||||
cameraErrorCallback = cameraErrorCallback,
|
||||
logger = logger
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package io.fotoapparat;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Switches between different instances of {@link Fotoapparat}. Convenient when you want to allow
|
||||
* user to switch between different cameras or configurations.
|
||||
* <p>
|
||||
* This class is not thread safe. Consider using it from a single thread.
|
||||
*/
|
||||
public class FotoapparatSwitcher {
|
||||
|
||||
@NonNull
|
||||
private Fotoapparat fotoapparat;
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
private FotoapparatSwitcher(@NonNull Fotoapparat fotoapparat) {
|
||||
this.fotoapparat = fotoapparat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link FotoapparatSwitcher} with given {@link Fotoapparat} used by default.
|
||||
*/
|
||||
public static FotoapparatSwitcher withDefault(@NonNull Fotoapparat fotoapparat) {
|
||||
return new FotoapparatSwitcher(fotoapparat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts {@link Fotoapparat} associated with this switcher. Every new {@link Fotoapparat} will
|
||||
* be started automatically until {@link #stop()} is called.
|
||||
*
|
||||
* @throws IllegalStateException if switcher is already started.
|
||||
*/
|
||||
public void start() {
|
||||
fotoapparat.start();
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops currently used {@link Fotoapparat}.
|
||||
*
|
||||
* @throws IllegalStateException if switcher is already stopped.
|
||||
*/
|
||||
public void stop() {
|
||||
fotoapparat.stop();
|
||||
|
||||
started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to another {@link Fotoapparat}. If switcher is already started then previously used
|
||||
* {@link Fotoapparat} will be stopped automatically and new {@link Fotoapparat} will be
|
||||
* started.
|
||||
*
|
||||
* @param fotoapparat new {@link Fotoapparat} to use.
|
||||
* @throws NullPointerException if given {@link Fotoapparat} is {@code null}.
|
||||
*/
|
||||
public void switchTo(@NonNull Fotoapparat fotoapparat) {
|
||||
if (started) {
|
||||
this.fotoapparat.stop();
|
||||
fotoapparat.start();
|
||||
}
|
||||
|
||||
this.fotoapparat = fotoapparat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return currently used instance of {@link Fotoapparat}.
|
||||
*/
|
||||
@NonNull
|
||||
public Fotoapparat getCurrentFotoapparat() {
|
||||
return fotoapparat;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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 { it.toAntiBandingMode() },
|
||||
sensorSensitivities = sensorSensitivities.toSet(),
|
||||
previewFpsRanges = supportedPreviewFpsRanges.extract { it.toFpsRange() },
|
||||
pictureResolutions = pictureResolutions.mapSizes(),
|
||||
previewResolutions = previewResolutions.mapSizes()
|
||||
)
|
||||
}
|
||||
|
||||
private fun <Parameter : Any, Code> List<Code>.extract(converter: (Code) -> Parameter?) = mapNotNull { converter(it) }.toSet()
|
||||
|
||||
private fun Collection<Camera.Size>.mapSizes() = map { it.toResolution() }.toSet()
|
||||
@@ -0,0 +1,20 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
import android.hardware.Camera
|
||||
|
||||
/**
|
||||
* Returns the [Characteristics] for the given `cameraId`.
|
||||
*/
|
||||
internal fun getCharacteristics(cameraId: Int): Characteristics {
|
||||
val info = Camera.CameraInfo()
|
||||
Camera.getCameraInfo(cameraId, info)
|
||||
val lensPosition = info.facing.toLensPosition()
|
||||
return Characteristics(
|
||||
cameraId = cameraId,
|
||||
lensPosition = lensPosition,
|
||||
orientation = info.orientation,
|
||||
isMirrored = lensPosition == LensPosition.Front
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
/**
|
||||
* A camera characteristic.
|
||||
*/
|
||||
interface Characteristic
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
/**
|
||||
* A set of information about the camera.
|
||||
*/
|
||||
internal data class Characteristics(
|
||||
val cameraId: Int,
|
||||
val lensPosition: LensPosition,
|
||||
val orientation: Int,
|
||||
val isMirrored: Boolean
|
||||
)
|
||||
+6
-6
@@ -1,23 +1,23 @@
|
||||
package io.fotoapparat.parameter;
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
/**
|
||||
* The camera position relatively to the screen of the device.
|
||||
*/
|
||||
public enum LensPosition {
|
||||
sealed class LensPosition : Characteristic {
|
||||
|
||||
/**
|
||||
* The back camera.
|
||||
*/
|
||||
BACK,
|
||||
object Back : LensPosition()
|
||||
|
||||
/**
|
||||
* The front camera.
|
||||
*/
|
||||
FRONT,
|
||||
object Front : LensPosition()
|
||||
|
||||
/**
|
||||
* An external camera.
|
||||
*/
|
||||
EXTERNAL
|
||||
object External : LensPosition()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
|
||||
/**
|
||||
* Maps between [LensPosition] and Camera v1 lens position code id.
|
||||
*
|
||||
* @receiver Camera facing info id.
|
||||
* @return [LensPosition] from the given lens position code id.
|
||||
* `null` if position code id is not supported.
|
||||
*/
|
||||
internal fun Int.toLensPosition(): LensPosition {
|
||||
return when (this) {
|
||||
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
|
||||
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
|
||||
else -> throw IllegalArgumentException("Lens position $this is not supported.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps between [LensPosition] and Camera v1 code id.
|
||||
*
|
||||
* @receiver [LensPosition]
|
||||
* @return code of the camera as in [Camera.CameraInfo].
|
||||
*/
|
||||
fun LensPosition.toCameraId(): Int {
|
||||
return (0 until Camera.getNumberOfCameras())
|
||||
.find {
|
||||
this == getCharacteristics(it).lensPosition
|
||||
}
|
||||
?: throw CameraException("Device has no camera for the desired lens position(s).")
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.preview.FrameProcessor
|
||||
import io.fotoapparat.selector.*
|
||||
|
||||
|
||||
private const val DEFAULT_JPEG_QUALITY = 90
|
||||
|
||||
/**
|
||||
* A camera configuration which has all it's selectors defined.
|
||||
*/
|
||||
data class CameraConfiguration(
|
||||
override val flashMode: Iterable<Flash>.() -> Flash? = off(),
|
||||
override val focusMode: Iterable<FocusMode>.() -> FocusMode? = firstAvailable(
|
||||
continuousFocusPicture(),
|
||||
autoFocus(),
|
||||
fixed()
|
||||
),
|
||||
override val jpegQuality: (IntRange) -> Int? = manualJpegQuality(DEFAULT_JPEG_QUALITY),
|
||||
override val frameProcessor: (Frame) -> Unit = {},
|
||||
override val previewFpsRange: Iterable<FpsRange>.() -> FpsRange? = highestFps(),
|
||||
override val antiBandingMode: Iterable<AntiBandingMode>.() -> AntiBandingMode? = firstAvailable(
|
||||
auto(),
|
||||
hz50(),
|
||||
hz60(),
|
||||
none()
|
||||
),
|
||||
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
|
||||
override val pictureResolution: Iterable<Resolution>.() -> Resolution? = highestResolution(),
|
||||
override val previewResolution: Iterable<Resolution>.() -> Resolution? = highestResolution()
|
||||
) : Configuration {
|
||||
|
||||
/**
|
||||
* Builder for [CameraConfiguration].
|
||||
*/
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var cameraConfiguration: CameraConfiguration = default()
|
||||
|
||||
fun flash(selector: (Iterable<Flash>.() -> Flash?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun focusMode(selector: (Iterable<FocusMode>.() -> FocusMode?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewFpsRange(selector: (Iterable<FpsRange>.() -> FpsRange?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun sensorSensitivity(selector: (Iterable<Int>.() -> Int?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
|
||||
cameraConfiguration.copy(
|
||||
antiBandingMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
|
||||
cameraConfiguration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun photoResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun frameProcessor(frameProcessor: FrameProcessor): Builder {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
frameProcessor = { frameProcessor.process(it) }
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new [CameraConfiguration].
|
||||
*/
|
||||
fun build(): CameraConfiguration = cameraConfiguration
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Alias for [CameraConfiguration.default]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun standard() = default()
|
||||
|
||||
/**
|
||||
* Default [CameraConfiguration].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun default() = CameraConfiguration()
|
||||
|
||||
/**
|
||||
* Creates a new [CameraConfiguration.Builder].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder() = CameraConfiguration.Builder()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
|
||||
interface Configuration {
|
||||
val flashMode: (Iterable<Flash>.() -> Flash?)?
|
||||
val focusMode: (Iterable<FocusMode>.() -> FocusMode?)?
|
||||
val jpegQuality: ((IntRange) -> Int?)?
|
||||
val frameProcessor: ((Frame) -> Unit)?
|
||||
val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)?
|
||||
val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)?
|
||||
val sensorSensitivity: (Iterable<Int>.() -> Int?)?
|
||||
val previewResolution: (Iterable<Resolution>.() -> Resolution?)?
|
||||
val pictureResolution: (Iterable<Resolution>.() -> Resolution?)?
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
|
||||
/**
|
||||
* A camera update configuration.
|
||||
*/
|
||||
data class UpdateConfiguration(
|
||||
override val flashMode: (Iterable<Flash>.() -> Flash?)? = null,
|
||||
override val focusMode: (Iterable<FocusMode>.() -> FocusMode?)? = null,
|
||||
override val jpegQuality: ((IntRange) -> Int?)? = null,
|
||||
override val frameProcessor: ((Frame) -> Unit)? = null,
|
||||
override val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)? = null,
|
||||
override val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)? = null,
|
||||
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
|
||||
override val previewResolution: (Iterable<Resolution>.() -> Resolution?)? = null,
|
||||
override val pictureResolution: (Iterable<Resolution>.() -> Resolution?)? = null
|
||||
) : Configuration {
|
||||
|
||||
/**
|
||||
* Builder for [UpdateConfiguration].
|
||||
*/
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var configuration = UpdateConfiguration()
|
||||
|
||||
fun flash(selector: Iterable<Flash>.() -> Flash?): Builder {
|
||||
configuration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): Builder {
|
||||
configuration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): Builder {
|
||||
configuration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): Builder {
|
||||
configuration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
|
||||
configuration.copy(
|
||||
antiBandingMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
|
||||
configuration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
|
||||
configuration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
|
||||
configuration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun frameProcessor(frameProcessor: (Frame) -> Unit): Builder {
|
||||
configuration.copy(
|
||||
frameProcessor = frameProcessor
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new [UpdateConfiguration].
|
||||
*/
|
||||
fun build(): UpdateConfiguration = configuration
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Creates a new [UpdateConfiguration.Builder].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder() = UpdateConfiguration.Builder()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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 defered: CompletableDeferred<Boolean> = CompletableDeferred()
|
||||
) : BroadcastChannel<T> by channel, Deferred<Boolean> by defered {
|
||||
|
||||
/**
|
||||
* The most recently sent element to this channel.
|
||||
*/
|
||||
suspend fun getValue(): T {
|
||||
defered.await()
|
||||
return channel.value
|
||||
}
|
||||
|
||||
override fun offer(element: T): Boolean {
|
||||
defered.complete(true)
|
||||
return channel.offer(element)
|
||||
}
|
||||
|
||||
suspend override fun send(element: T) {
|
||||
defered.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 android.os.Looper
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.hardware.executeMainThread
|
||||
|
||||
/**
|
||||
* @return CameraErrorCallback which will always move execution to the main thread.
|
||||
*/
|
||||
fun ((CameraException) -> Unit).onMainThread(): (CameraException) -> Unit = { cameraException ->
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
this(cameraException)
|
||||
} else {
|
||||
executeMainThread { this(cameraException) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.fotoapparat.exception
|
||||
|
||||
/**
|
||||
* Thrown when there is a problem while saving the file.
|
||||
*/
|
||||
class FileSaveException(cause: Throwable) : RuntimeException(cause)
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.fotoapparat.exception
|
||||
|
||||
/**
|
||||
* Thrown when zoom level is outside of [0..1] range.
|
||||
*/
|
||||
class LevelOutOfRangeException(zoomLevel: Float) : RuntimeException(zoomLevel.toString() + " is out of range [0..1]")
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.fotoapparat.exception
|
||||
|
||||
/**
|
||||
* Exception which is not caused by developer and can be either ignored or recovered from.
|
||||
*/
|
||||
open class RecoverableRuntimeException : RuntimeException {
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(throwable: Throwable) : super(throwable)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.fotoapparat.exception
|
||||
|
||||
/**
|
||||
* Thrown when it is not possible to decode bitmap from byte array.
|
||||
*/
|
||||
class UnableToDecodeBitmapException : RecoverableRuntimeException("Unable to decode bitmap")
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.fotoapparat.exception.camera
|
||||
|
||||
/**
|
||||
* A generic camera exception.
|
||||
*/
|
||||
open class CameraException(
|
||||
message: String,
|
||||
cause: Throwable? = null
|
||||
) : RuntimeException(
|
||||
message,
|
||||
cause
|
||||
)
|
||||
+30
@@ -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}."
|
||||
)
|
||||
}
|
||||
+9
@@ -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."
|
||||
)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.fotoapparat.exception.camera
|
||||
|
||||
import io.fotoapparat.parameter.Parameter
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a configuration selector couldn't select a value.
|
||||
*/
|
||||
internal class UnsupportedConfigurationException : CameraException {
|
||||
constructor(
|
||||
klass: Class<out Parameter>,
|
||||
supportedParameters: Collection<Parameter>
|
||||
) : super(
|
||||
"${klass.simpleName} configuration selector couldn't select a value. " +
|
||||
"Supported parameters: $supportedParameters"
|
||||
)
|
||||
|
||||
constructor(
|
||||
configurationName: String,
|
||||
supportedRange: ClosedRange<*>
|
||||
) : super(
|
||||
"$configurationName configuration selector couldn't select a value. " +
|
||||
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.fotoapparat.exception.camera
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the device has no camera for the desired lens position(s).
|
||||
*/
|
||||
class UnsupportedLensException : CameraException("Device has no camera for the desired lens position(s).")
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.fotoapparat.exif
|
||||
|
||||
import io.fotoapparat.result.Photo
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Writes Exif orientation attributes.
|
||||
*/
|
||||
internal interface ExifOrientationWriter {
|
||||
|
||||
/**
|
||||
* Writes EXIF orientation tag into a file, overwriting it if it already exists.
|
||||
*
|
||||
* @param file File of the image.
|
||||
* @param photo Photo stored in the file.
|
||||
* @throws FileSaveException If writing has failed.
|
||||
*/
|
||||
fun writeExifOrientation(file: File, photo: Photo)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.fotoapparat.exif
|
||||
|
||||
import android.media.ExifInterface
|
||||
import io.fotoapparat.exception.FileSaveException
|
||||
import io.fotoapparat.result.Photo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Writes Exif attributes.
|
||||
*/
|
||||
internal object ExifWriter : ExifOrientationWriter {
|
||||
|
||||
|
||||
@Throws(FileSaveException::class)
|
||||
override fun writeExifOrientation(file: File, photo: Photo) {
|
||||
try {
|
||||
val exifInterface = ExifInterface(file.path)
|
||||
exifInterface.setAttribute(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
toExifOrientation(photo.rotationDegrees).toString()
|
||||
)
|
||||
exifInterface.saveAttributes()
|
||||
} catch (e: IOException) {
|
||||
throw FileSaveException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toExifOrientation(rotationDegrees: Int): Int {
|
||||
val compensationRotationDegrees = (360 - rotationDegrees) % 360
|
||||
|
||||
return when (compensationRotationDegrees) {
|
||||
90 -> ExifInterface.ORIENTATION_ROTATE_90
|
||||
180 -> ExifInterface.ORIENTATION_ROTATE_180
|
||||
270 -> ExifInterface.ORIENTATION_ROTATE_270
|
||||
else -> ExifInterface.ORIENTATION_NORMAL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
@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.computeDisplayOrientation
|
||||
import io.fotoapparat.hardware.orientation.computeImageOrientation
|
||||
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.Frame
|
||||
import io.fotoapparat.preview.PreviewStream
|
||||
import io.fotoapparat.result.FocusResult
|
||||
import io.fotoapparat.result.Photo
|
||||
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 displayRotation = 0
|
||||
var imageRotation = 0
|
||||
|
||||
/**
|
||||
* Opens a connection to a camera.
|
||||
*/
|
||||
open fun open() {
|
||||
logger.recordMethod()
|
||||
|
||||
val lensPosition = characteristics.lensPosition
|
||||
val cameraId = lensPosition.toCameraId()
|
||||
|
||||
try {
|
||||
camera = Camera.open(cameraId)
|
||||
capabilities.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(imageRotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: ((Frame) -> Unit)?) {
|
||||
logger.recordMethod()
|
||||
|
||||
previewStream.updateProcessorSafely(frameProcessor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current orientation of the display.
|
||||
*/
|
||||
open fun setDisplayOrientation(degrees: Int) {
|
||||
logger.recordMethod()
|
||||
|
||||
imageRotation = computeImageOrientation(
|
||||
degrees = degrees,
|
||||
characteristics = characteristics
|
||||
)
|
||||
|
||||
displayRotation = computeDisplayOrientation(
|
||||
degrees = degrees,
|
||||
characteristics = characteristics
|
||||
)
|
||||
|
||||
logger.log("Image Rotation is: $imageRotation. Display rotation is: $displayRotation")
|
||||
|
||||
previewStream.frameOrientation = imageRotation
|
||||
camera.setDisplayOrientation(displayRotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes zoom level of the camera. Must be called only if zoom is supported.
|
||||
*
|
||||
* @param level normalized zoom level. Value in range [0..1].
|
||||
*/
|
||||
open fun setZoom(@FloatRange(from = 0.0, to = 1.0) level: Float) {
|
||||
logger.recordMethod()
|
||||
|
||||
setZoomSafely(level)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs auto focus. This is a blocking operation which returns the result of the operation
|
||||
* when auto focus completes.
|
||||
*/
|
||||
open fun autoFocus(): FocusResult {
|
||||
logger.recordMethod()
|
||||
|
||||
return camera.focusSafely()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 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.
|
||||
*/
|
||||
open fun setDisplaySurface(preview: Preview) {
|
||||
logger.recordMethod()
|
||||
|
||||
surface = camera.setDisplaySurface(preview)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the camera to the [MediaRecorder].
|
||||
*/
|
||||
open fun attachRecordingCamera(mediaRecorder: MediaRecorder) {
|
||||
logger.recordMethod()
|
||||
|
||||
mediaRecorder.setCamera(camera)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [Resolution] of the displayed preview.
|
||||
*/
|
||||
open fun getPreviewResolution(): Resolution {
|
||||
logger.recordMethod()
|
||||
|
||||
val previewResolution = camera.getPreviewResolution(imageRotation)
|
||||
|
||||
logger.log("Preview resolution is: $previewResolution")
|
||||
|
||||
return previewResolution
|
||||
}
|
||||
|
||||
|
||||
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
|
||||
try {
|
||||
setZoomUnsafe(level)
|
||||
} catch (e: Exception) {
|
||||
logger.log("Unable to change zoom level to " + level + " e: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setZoomUnsafe(@FloatRange(from = 0.0, to = 1.0) level: Float) {
|
||||
(cachedZoomParameters ?: camera.parameters)
|
||||
.apply {
|
||||
zoom = (maxZoom * level).toInt()
|
||||
}
|
||||
.let {
|
||||
cachedZoomParameters = it
|
||||
camera.parameters = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun Camera.focusSafely(): FocusResult {
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
try {
|
||||
autoFocus { _, _ -> latch.countDown() }
|
||||
} catch (e: Exception) {
|
||||
logger.log("Failed to perform autofocus using device ${characteristics.cameraId} e: ${e.message}")
|
||||
|
||||
return FocusResult.UnableToFocus
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await(AUTOFOCUS_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return FocusResult.Focused
|
||||
}
|
||||
|
||||
private suspend fun Camera.updateFocusingAreas(focalRequest: FocalRequest) {
|
||||
val focusingAreas = focalRequest.toFocusAreas(
|
||||
displayOrientationDegrees = displayRotation,
|
||||
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 computeImageOrientation(
|
||||
degrees: Int,
|
||||
characteristics: Characteristics
|
||||
) = computeImageOrientation(
|
||||
screenRotationDegrees = degrees,
|
||||
cameraRotationDegrees = characteristics.orientation,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
private fun computeDisplayOrientation(
|
||||
degrees: Int,
|
||||
characteristics: Characteristics
|
||||
) = computeDisplayOrientation(
|
||||
screenRotationDegrees = degrees,
|
||||
cameraRotationDegrees = characteristics.orientation,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
private fun Camera.updateParameters(newParameters: CameraParameters) {
|
||||
parameters = parameters.applyNewParameters(newParameters)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun Camera.setDisplaySurface(
|
||||
preview: Preview
|
||||
): Surface = when (preview) {
|
||||
is Preview.Texture -> preview.surfaceTexture
|
||||
.also {
|
||||
setPreviewTexture(it)
|
||||
}
|
||||
.let {
|
||||
Surface(it)
|
||||
}
|
||||
is Preview.Surface -> preview.surfaceHolder
|
||||
.also {
|
||||
setPreviewDisplay(it)
|
||||
}
|
||||
.surface
|
||||
}
|
||||
|
||||
private fun Camera.getPreviewResolution(imageRotation: Int): Resolution {
|
||||
val previewSize = parameters.previewSize
|
||||
|
||||
val size = PreviewSize(
|
||||
previewSize.width,
|
||||
previewSize.height
|
||||
)
|
||||
|
||||
return size.run {
|
||||
when (imageRotation) {
|
||||
0, 180 -> this
|
||||
else -> flipDimensions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PreviewStream.updateProcessorSafely(frameProcessor: ((Frame) -> Unit)?) {
|
||||
clearProcessors()
|
||||
when (frameProcessor) {
|
||||
null -> stop()
|
||||
else -> {
|
||||
addProcessor(frameProcessor)
|
||||
start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Capabilities.canSetFocusingAreas(): Boolean {
|
||||
return 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,123 +0,0 @@
|
||||
package io.fotoapparat.hardware;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import io.fotoapparat.parameter.Flash;
|
||||
import io.fotoapparat.parameter.FocusMode;
|
||||
import io.fotoapparat.parameter.Size;
|
||||
|
||||
/**
|
||||
* Capabilities of camera hardware.
|
||||
*/
|
||||
public class Capabilities {
|
||||
|
||||
@NonNull
|
||||
private final Set<Size> photoSizes;
|
||||
@NonNull
|
||||
private final Set<Size> previewSizes;
|
||||
@NonNull
|
||||
private final Set<FocusMode> focusModes;
|
||||
@NonNull
|
||||
private final Set<Flash> flashModes;
|
||||
private final boolean zoomSupported;
|
||||
|
||||
public Capabilities(@NonNull Set<Size> photoSizes,
|
||||
@NonNull Set<Size> previewSizes,
|
||||
@NonNull Set<FocusMode> focusModes,
|
||||
@NonNull Set<Flash> flashModes,
|
||||
boolean zoomSupported) {
|
||||
this.photoSizes = photoSizes;
|
||||
this.previewSizes = previewSizes;
|
||||
this.focusModes = focusModes;
|
||||
this.flashModes = flashModes;
|
||||
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(),
|
||||
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 {@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 == null || getClass() != o.getClass()) 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);
|
||||
|
||||
}
|
||||
|
||||
@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 + (zoomSupported ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Capabilities{" +
|
||||
"photoSizes=" + photoSizes +
|
||||
", previewSizes=" + previewSizes +
|
||||
", focusModes=" + focusModes +
|
||||
", flashModes=" + flashModes +
|
||||
", zoomSupported=" + zoomSupported +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.fotoapparat.hardware
|
||||
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.characteristic.getCharacteristics
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.configuration.Configuration
|
||||
import io.fotoapparat.exception.camera.UnsupportedLensException
|
||||
import io.fotoapparat.hardware.display.Display
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.parameter.camera.provide.getCameraParameters
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import io.fotoapparat.view.FocalPointSelector
|
||||
import kotlinx.coroutines.experimental.CompletableDeferred
|
||||
|
||||
|
||||
/**
|
||||
* Phone.
|
||||
*/
|
||||
internal open class Device(
|
||||
private val logger: Logger,
|
||||
private val display: Display,
|
||||
open val scaleType: ScaleType,
|
||||
open val cameraRenderer: CameraRenderer,
|
||||
val focusPointSelector: FocalPointSelector?,
|
||||
numberOfCameras: Int = Camera.getNumberOfCameras(),
|
||||
initialConfiguration: CameraConfiguration, initialLensPositionSelector: Collection<LensPosition>.() -> LensPosition?
|
||||
) {
|
||||
|
||||
private val cameras = (0 until numberOfCameras).map { cameraId ->
|
||||
CameraDevice(
|
||||
logger = logger,
|
||||
characteristics = getCharacteristics(cameraId)
|
||||
)
|
||||
}
|
||||
|
||||
private var lensPositionSelector: Collection<LensPosition>.() -> LensPosition? = initialLensPositionSelector
|
||||
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
|
||||
private var savedConfiguration = CameraConfiguration.default()
|
||||
|
||||
init {
|
||||
updateLensPositionSelector(initialLensPositionSelector)
|
||||
savedConfiguration = initialConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a camera.
|
||||
*/
|
||||
open fun canSelectCamera(lensPositionSelector: (Collection<LensPosition>) -> LensPosition?): Boolean {
|
||||
val selectedCameraDevice = selectCamera(
|
||||
availableCameras = cameras,
|
||||
lensPositionSelector = lensPositionSelector
|
||||
)
|
||||
return selectedCameraDevice != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a camera. Will do nothing if camera cannot be selected.
|
||||
*/
|
||||
open fun selectCamera() {
|
||||
logger.recordMethod()
|
||||
|
||||
selectCamera(
|
||||
availableCameras = cameras,
|
||||
lensPositionSelector = lensPositionSelector
|
||||
)
|
||||
?.let {
|
||||
selectedCameraDevice.complete(it)
|
||||
}
|
||||
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the selected camera.
|
||||
*/
|
||||
open fun clearSelectedCamera() {
|
||||
selectedCameraDevice = CompletableDeferred()
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits and returns the selected camera.
|
||||
*/
|
||||
open suspend fun awaitSelectedCamera(): CameraDevice {
|
||||
return selectedCameraDevice.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected camera.
|
||||
*
|
||||
* @throws IllegalStateException If no camera has been yet selected.
|
||||
* @throws UnsupportedLensException If no camera could get selected.
|
||||
*/
|
||||
open fun getSelectedCamera(): CameraDevice {
|
||||
return try {
|
||||
selectedCameraDevice.getCompleted()
|
||||
} catch (e: IllegalStateException) {
|
||||
throw IllegalStateException("Camera has not started!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if a camera has been selected.
|
||||
*/
|
||||
open fun hasSelectedCamera() = selectedCameraDevice.isCompleted
|
||||
|
||||
/**
|
||||
* @return rotation of the screen in degrees.
|
||||
*/
|
||||
open fun getScreenRotation(): Int {
|
||||
return display.getRotation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the desired from the user camera lens position.
|
||||
*/
|
||||
open fun updateLensPositionSelector(newLensPosition: Collection<LensPosition>.() -> LensPosition?) {
|
||||
logger.recordMethod()
|
||||
|
||||
lensPositionSelector = newLensPosition
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the desired from the user selectors.
|
||||
*/
|
||||
open fun updateConfiguration(newConfiguration: Configuration) {
|
||||
logger.recordMethod()
|
||||
|
||||
savedConfiguration = updateConfiguration(
|
||||
savedConfiguration = savedConfiguration,
|
||||
newConfiguration = newConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The desired from the user selectors.
|
||||
*/
|
||||
open fun getConfiguration(): CameraConfiguration {
|
||||
return savedConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The selected [CameraParameters] for the given [CameraDevice].
|
||||
*/
|
||||
open suspend fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters {
|
||||
return getCameraParameters(
|
||||
cameraConfiguration = savedConfiguration,
|
||||
capabilities = cameraDevice.getCapabilities()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The frame processor.
|
||||
*/
|
||||
open fun getFrameProcessor(): (Frame) -> Unit {
|
||||
return savedConfiguration.frameProcessor
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The desired from the user camera lens position.
|
||||
*/
|
||||
open fun getLensPositionSelector(): Collection<LensPosition>.() -> LensPosition? {
|
||||
return lensPositionSelector
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the device's configuration.
|
||||
*/
|
||||
internal fun updateConfiguration(
|
||||
savedConfiguration: CameraConfiguration,
|
||||
newConfiguration: Configuration
|
||||
) = CameraConfiguration(
|
||||
flashMode = newConfiguration.flashMode ?: savedConfiguration.flashMode,
|
||||
focusMode = newConfiguration.focusMode ?: savedConfiguration.focusMode,
|
||||
frameProcessor = newConfiguration.frameProcessor ?: savedConfiguration.frameProcessor,
|
||||
previewFpsRange = newConfiguration.previewFpsRange ?: savedConfiguration.previewFpsRange,
|
||||
sensorSensitivity = newConfiguration.sensorSensitivity ?: savedConfiguration.sensorSensitivity,
|
||||
pictureResolution = newConfiguration.pictureResolution ?: savedConfiguration.pictureResolution,
|
||||
previewResolution = newConfiguration.previewResolution ?: savedConfiguration.previewResolution
|
||||
)
|
||||
|
||||
/**
|
||||
* Selects a camera from the set of available ones.
|
||||
*/
|
||||
internal fun selectCamera(
|
||||
availableCameras: List<CameraDevice>,
|
||||
lensPositionSelector: Collection<LensPosition>.() -> LensPosition?
|
||||
): CameraDevice? {
|
||||
|
||||
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
|
||||
val desiredPosition = lensPositionSelector(lensPositions)
|
||||
|
||||
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.fotoapparat.hardware
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
private var taskExecutor = Executors.newSingleThreadExecutor()
|
||||
get() = field.takeUnless { it.isShutdown } ?: Executors.newSingleThreadExecutor().also { field = it }
|
||||
|
||||
private val cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
private val loggingExecutor = Executors.newSingleThreadExecutor()
|
||||
private val mainThreadHandler = Handler(Looper.getMainLooper())
|
||||
/**
|
||||
* [java.util.concurrent.Executor] operating the [io.fotoapparat.result.PendingResult].
|
||||
*/
|
||||
internal val pendingResultExecutor = Executors.newSingleThreadExecutor()
|
||||
/**
|
||||
* [java.util.concurrent.Executor] operating the [io.fotoapparat.preview.PreviewStream].
|
||||
*/
|
||||
internal val frameProcessingExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
/**
|
||||
* Shuts down all pending camera tasks.
|
||||
*/
|
||||
internal fun shutdownPendingTasks() {
|
||||
taskExecutor.shutdownNow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a camera task.
|
||||
*/
|
||||
internal fun executeTask(function: Runnable) = taskExecutor.execute(function)
|
||||
|
||||
/**
|
||||
* Executes a camera operation.
|
||||
*/
|
||||
internal fun execute(function: () -> Unit) = cameraExecutor.execute(function)
|
||||
|
||||
/**
|
||||
* Executes an operation in the main thread.
|
||||
*/
|
||||
internal fun executeLoggingThread(function: () -> Unit) = loggingExecutor.execute { function() }
|
||||
|
||||
/**
|
||||
* Executes an operation in the main thread.
|
||||
*/
|
||||
internal fun executeMainThread(function: () -> Unit) = mainThreadHandler.post { function() }
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.fotoapparat.hardware.display
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Surface
|
||||
import android.view.WindowManager
|
||||
|
||||
/**
|
||||
* A phone's display.
|
||||
*/
|
||||
open internal class Display(context: Context) {
|
||||
|
||||
private val display = context.getDisplay()
|
||||
|
||||
/**
|
||||
* Returns the rotation of the screen from its "natural" orientation in degrees.
|
||||
*/
|
||||
open fun getRotation(): Int = when (display.rotation) {
|
||||
Surface.ROTATION_90 -> 90
|
||||
Surface.ROTATION_180 -> 180
|
||||
Surface.ROTATION_270 -> 270
|
||||
Surface.ROTATION_0 -> 0
|
||||
else -> 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Context.getDisplay() = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
|
||||
@@ -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
|
||||
)
|
||||
+117
@@ -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();
|
||||
}
|
||||
-13
@@ -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();
|
||||
}
|
||||
-15
@@ -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();
|
||||
|
||||
}
|
||||
-15
@@ -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,61 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
|
||||
* front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return clockwise rotation of the image relatively to current device orientation.
|
||||
*/
|
||||
internal fun computeImageOrientation(
|
||||
screenRotationDegrees: Int,
|
||||
cameraRotationDegrees: Int,
|
||||
cameraIsMirrored: Boolean
|
||||
): Int {
|
||||
val rotation = if (cameraIsMirrored) {
|
||||
-(screenRotationDegrees + cameraRotationDegrees)
|
||||
} else {
|
||||
screenRotationDegrees - cameraRotationDegrees
|
||||
}
|
||||
|
||||
return (rotation + 720) % 360
|
||||
}
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
|
||||
* front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return display orientation in which user will see the output camera in a correct rotation.
|
||||
*/
|
||||
internal fun computeDisplayOrientation(
|
||||
screenRotationDegrees: Int,
|
||||
cameraRotationDegrees: Int,
|
||||
cameraIsMirrored: Boolean
|
||||
): Int {
|
||||
var degrees = toClosestRightAngle(screenRotationDegrees)
|
||||
|
||||
return if (cameraIsMirrored) {
|
||||
degrees = (cameraRotationDegrees + degrees) % 360
|
||||
(360 - degrees) % 360
|
||||
} else {
|
||||
(cameraRotationDegrees - degrees + 360) % 360
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return closest right angle to given value. That is: 0, 90, 180, 270.
|
||||
*/
|
||||
internal fun toClosestRightAngle(degrees: Int): Int {
|
||||
val roundUp = degrees % 90 > 45
|
||||
|
||||
val roundAppModifier = if (roundUp) 1 else 0
|
||||
|
||||
return (degrees / 90 + roundAppModifier) * 90 % 360
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package io.fotoapparat.hardware.orientation;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Monitors orientation of the device.
|
||||
*/
|
||||
public class OrientationSensor implements RotationListener.Listener {
|
||||
|
||||
private final RotationListener rotationListener;
|
||||
private final ScreenOrientationProvider screenOrientationProvider;
|
||||
|
||||
private int lastKnownRotation;
|
||||
private Listener listener;
|
||||
|
||||
public OrientationSensor(@NonNull final RotationListener rotationListener,
|
||||
@NonNull final ScreenOrientationProvider screenOrientationProvider) {
|
||||
this.rotationListener = rotationListener;
|
||||
this.screenOrientationProvider = screenOrientationProvider;
|
||||
|
||||
rotationListener.setRotationListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts monitoring device's orientation.
|
||||
*/
|
||||
public void start(Listener listener) {
|
||||
this.listener = listener;
|
||||
rotationListener.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops monitoring device's orientation.
|
||||
*/
|
||||
public void stop() {
|
||||
rotationListener.disable();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationChanged() {
|
||||
if (listener != null) {
|
||||
int rotation = screenOrientationProvider.getScreenRotation();
|
||||
if (rotation != lastKnownRotation) {
|
||||
listener.onOrientationChanged(rotation);
|
||||
lastKnownRotation = rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notified when orientation of the device is updated.
|
||||
*/
|
||||
public interface Listener {
|
||||
|
||||
/**
|
||||
* Called when orientation of the device is updated.
|
||||
*/
|
||||
void onOrientationChanged(int degrees);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
import android.content.Context
|
||||
import io.fotoapparat.hardware.Device
|
||||
|
||||
|
||||
/**
|
||||
* Monitors orientation of the device.
|
||||
*/
|
||||
open internal class OrientationSensor(
|
||||
private val rotationListener: RotationListener,
|
||||
private val device: Device
|
||||
) {
|
||||
|
||||
constructor(context: Context,
|
||||
device: Device
|
||||
) : this(
|
||||
RotationListener(context),
|
||||
device
|
||||
)
|
||||
|
||||
private val onOrientationChanged = {
|
||||
device.getScreenRotation().let {
|
||||
if (it != lastKnownRotation) {
|
||||
listener(it)
|
||||
lastKnownRotation = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
rotationListener.orientationChanged = onOrientationChanged
|
||||
}
|
||||
|
||||
private lateinit var listener: (Int) -> Unit
|
||||
private var lastKnownRotation: Int = 0
|
||||
|
||||
/**
|
||||
* Starts monitoring device's orientation.
|
||||
*/
|
||||
open fun start(listener: (Int) -> Unit) {
|
||||
this.listener = listener
|
||||
rotationListener.enable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops monitoring device's orientation.
|
||||
*/
|
||||
open fun stop() {
|
||||
rotationListener.disable()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package io.fotoapparat.hardware.orientation;
|
||||
|
||||
/**
|
||||
* Utilities for working with device orientation.
|
||||
*/
|
||||
public class OrientationUtils {
|
||||
|
||||
/**
|
||||
* @return closest right angle to given value. That is: 0, 90, 180, 270.
|
||||
*/
|
||||
public static int toClosestRightAngle(int degrees) {
|
||||
boolean roundUp = degrees % 90 > 45;
|
||||
|
||||
int roundAppModifier = roundUp ? 1 : 0;
|
||||
|
||||
return (((degrees / 90) + roundAppModifier) * 90) % 360;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
|
||||
* for front cameras). {@code false} if it is not mirrored.
|
||||
* @return display orientation in which user will see the output camera in a correct rotation.
|
||||
*/
|
||||
public static int computeDisplayOrientation(int screenRotationDegrees,
|
||||
int cameraRotationDegrees,
|
||||
boolean cameraIsMirrored) {
|
||||
int degrees = OrientationUtils.toClosestRightAngle(screenRotationDegrees);
|
||||
|
||||
if (cameraIsMirrored) {
|
||||
degrees = (cameraRotationDegrees + degrees) % 360;
|
||||
degrees = (360 - degrees) % 360;
|
||||
} else {
|
||||
degrees = (cameraRotationDegrees - degrees + 360) % 360;
|
||||
}
|
||||
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
|
||||
* for front cameras). {@code false} if it is not mirrored.
|
||||
* @return clockwise rotation of the image relatively to current device orientation.
|
||||
*/
|
||||
public static int computeImageOrientation(int screenRotationDegrees,
|
||||
int cameraRotationDegrees,
|
||||
boolean cameraIsMirrored) {
|
||||
int rotation;
|
||||
|
||||
if (cameraIsMirrored) {
|
||||
rotation = -(screenRotationDegrees + cameraRotationDegrees);
|
||||
} else {
|
||||
rotation = screenRotationDegrees - cameraRotationDegrees;
|
||||
}
|
||||
|
||||
return (rotation + 360 + 360) % 360;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package io.fotoapparat.hardware.orientation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.OrientationEventListener;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link OrientationEventListener} to notify when the device's rotation has changed.
|
||||
*/
|
||||
public class RotationListener extends OrientationEventListener {
|
||||
|
||||
private Listener listener;
|
||||
|
||||
public RotationListener(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
if (listener != null && canDetectOrientation()) {
|
||||
listener.onRotationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to this class to notify future rotation events.
|
||||
*
|
||||
* @param listener The new listener
|
||||
*/
|
||||
void setRotationListener(@NonNull Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notified when the rotation of the device is updated.
|
||||
*/
|
||||
interface Listener {
|
||||
|
||||
/**
|
||||
* Called when the rotation of the device has changed.
|
||||
*/
|
||||
void onRotationChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
import android.content.Context
|
||||
import android.view.OrientationEventListener
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper around [OrientationEventListener] to notify when the device's rotation has changed.
|
||||
*/
|
||||
open internal class RotationListener(
|
||||
context: Context
|
||||
) : OrientationEventListener(context) {
|
||||
|
||||
lateinit var orientationChanged: () -> Unit
|
||||
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
if (canDetectOrientation()) {
|
||||
orientationChanged()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-39
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-17
@@ -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);
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package io.fotoapparat.hardware.provider;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import io.fotoapparat.parameter.LensPosition;
|
||||
|
||||
/**
|
||||
* Provides available {@link LensPosition} using Camera v1 API.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class V1AvailableLensPositionProvider implements AvailableLensPositionsProvider {
|
||||
|
||||
@Override
|
||||
public List<LensPosition> getAvailableLensPositions() {
|
||||
HashSet<LensPosition> positions = new HashSet<>();
|
||||
|
||||
final int numberOfCameras = Camera.getNumberOfCameras();
|
||||
|
||||
for (int i = 0; i < numberOfCameras; i++) {
|
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(i, cameraInfo);
|
||||
|
||||
positions.add(
|
||||
cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
|
||||
? LensPosition.FRONT
|
||||
: LensPosition.BACK
|
||||
);
|
||||
}
|
||||
|
||||
return new ArrayList<>(positions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package io.fotoapparat.hardware.provider;
|
||||
|
||||
import io.fotoapparat.hardware.CameraDevice;
|
||||
import io.fotoapparat.hardware.v1.Camera1;
|
||||
import io.fotoapparat.log.Logger;
|
||||
|
||||
/**
|
||||
* Always provides {@link Camera1}.
|
||||
*/
|
||||
public class V1Provider implements CameraProvider {
|
||||
|
||||
@Override
|
||||
public CameraDevice get(Logger logger) {
|
||||
return new Camera1(logger);
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
package io.fotoapparat.hardware.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import io.fotoapparat.hardware.CameraException;
|
||||
import io.fotoapparat.parameter.LensPosition;
|
||||
|
||||
import static io.fotoapparat.hardware.v2.parameters.converters.LensPositionConverter.toLensPosition;
|
||||
|
||||
/**
|
||||
* Provides available {@link LensPosition} using Camera v2 API.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public class V2AvailableLensPositionProvider implements AvailableLensPositionsProvider {
|
||||
|
||||
private final CameraManager manager;
|
||||
|
||||
public V2AvailableLensPositionProvider(Context context) {
|
||||
manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LensPosition> getAvailableLensPositions() {
|
||||
Set<LensPosition> positions = new HashSet<>();
|
||||
|
||||
String[] cameraIdList = getCameraIdListUnsafe();
|
||||
|
||||
for (String cameraId : cameraIdList) {
|
||||
Integer lensFacingConstant = getLensPositionUnsafe(cameraId);
|
||||
positions.add(toLensPosition(lensFacingConstant));
|
||||
}
|
||||
|
||||
return new ArrayList<>(positions);
|
||||
}
|
||||
|
||||
private Integer getLensPositionUnsafe(String cameraId) {
|
||||
try {
|
||||
return manager
|
||||
.getCameraCharacteristics(cameraId)
|
||||
.get(CameraCharacteristics.LENS_FACING);
|
||||
} catch (CameraAccessException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getCameraIdListUnsafe() {
|
||||
try {
|
||||
return manager.getCameraIdList();
|
||||
} catch (CameraAccessException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package io.fotoapparat.hardware.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import io.fotoapparat.hardware.CameraDevice;
|
||||
import io.fotoapparat.hardware.v2.Camera2;
|
||||
import io.fotoapparat.hardware.v2.CameraThread;
|
||||
import io.fotoapparat.hardware.v2.capabilities.CapabilitiesFactory;
|
||||
import io.fotoapparat.hardware.v2.connection.CameraConnection;
|
||||
import io.fotoapparat.hardware.v2.lens.executors.CaptureOperatorImpl;
|
||||
import io.fotoapparat.hardware.v2.lens.executors.ExposureGatheringExecutor;
|
||||
import io.fotoapparat.hardware.v2.lens.executors.FocusExecutor;
|
||||
import io.fotoapparat.hardware.v2.lens.operations.LensOperationsFactory;
|
||||
import io.fotoapparat.hardware.v2.orientation.OrientationManager;
|
||||
import io.fotoapparat.hardware.v2.parameters.CaptureRequestFactory;
|
||||
import io.fotoapparat.hardware.v2.parameters.ParametersProvider;
|
||||
import io.fotoapparat.hardware.v2.parameters.RendererParametersProvider;
|
||||
import io.fotoapparat.hardware.v2.readers.ContinuousSurfaceReader;
|
||||
import io.fotoapparat.hardware.v2.readers.StillSurfaceReader;
|
||||
import io.fotoapparat.hardware.v2.selection.CameraSelector;
|
||||
import io.fotoapparat.hardware.v2.session.SessionManager;
|
||||
import io.fotoapparat.hardware.v2.session.SessionProvider;
|
||||
import io.fotoapparat.hardware.v2.stream.PreviewStream2;
|
||||
import io.fotoapparat.hardware.v2.surface.TextureManager;
|
||||
import io.fotoapparat.log.Logger;
|
||||
|
||||
/**
|
||||
* Always provides {@link Camera2}.
|
||||
*/
|
||||
public class V2Provider implements CameraProvider {
|
||||
|
||||
private static final CameraThread CAMERA_THREAD = new CameraThread();
|
||||
private final Context context;
|
||||
|
||||
public V2Provider(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public CameraDevice get(Logger logger) {
|
||||
|
||||
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
AvailableLensPositionsProvider availableLensPositionsProvider = new V2AvailableLensPositionProvider(
|
||||
context
|
||||
);
|
||||
|
||||
CameraSelector cameraSelector = new CameraSelector(manager);
|
||||
|
||||
CameraConnection cameraConnection = new CameraConnection(
|
||||
cameraSelector,
|
||||
manager,
|
||||
CAMERA_THREAD
|
||||
);
|
||||
|
||||
ParametersProvider parametersProvider = new ParametersProvider();
|
||||
|
||||
OrientationManager orientationManager = new OrientationManager(
|
||||
cameraConnection
|
||||
);
|
||||
|
||||
StillSurfaceReader stillSurfaceReader = new StillSurfaceReader(
|
||||
parametersProvider,
|
||||
CAMERA_THREAD
|
||||
);
|
||||
ContinuousSurfaceReader continuousSurfaceReader = new ContinuousSurfaceReader(
|
||||
parametersProvider,
|
||||
CAMERA_THREAD
|
||||
);
|
||||
TextureManager textureManager = new TextureManager(
|
||||
orientationManager,
|
||||
parametersProvider
|
||||
);
|
||||
|
||||
CaptureRequestFactory captureRequestFactory = new CaptureRequestFactory(
|
||||
cameraConnection,
|
||||
stillSurfaceReader,
|
||||
textureManager,
|
||||
parametersProvider
|
||||
);
|
||||
|
||||
SessionProvider sessionProvider = new SessionProvider(
|
||||
stillSurfaceReader,
|
||||
cameraConnection,
|
||||
captureRequestFactory,
|
||||
textureManager,
|
||||
CAMERA_THREAD
|
||||
);
|
||||
|
||||
SessionManager sessionManager = new SessionManager(
|
||||
cameraConnection,
|
||||
sessionProvider
|
||||
);
|
||||
|
||||
CapabilitiesFactory capabilitiesOperator = new CapabilitiesFactory(cameraConnection);
|
||||
|
||||
PreviewStream2 previewStream = new PreviewStream2(
|
||||
continuousSurfaceReader,
|
||||
parametersProvider,
|
||||
logger
|
||||
);
|
||||
|
||||
RendererParametersProvider rendererParametersOperator = new RendererParametersProvider(
|
||||
parametersProvider,
|
||||
orientationManager
|
||||
);
|
||||
|
||||
LensOperationsFactory lensOperationsFactory = new LensOperationsFactory(
|
||||
sessionManager,
|
||||
captureRequestFactory,
|
||||
CAMERA_THREAD
|
||||
);
|
||||
|
||||
FocusExecutor focusExecutor = new FocusExecutor(
|
||||
parametersProvider,
|
||||
lensOperationsFactory
|
||||
);
|
||||
ExposureGatheringExecutor exposureGatheringExecutor = new ExposureGatheringExecutor(
|
||||
lensOperationsFactory
|
||||
);
|
||||
CaptureOperatorImpl captureExecutor = new CaptureOperatorImpl(
|
||||
lensOperationsFactory,
|
||||
stillSurfaceReader,
|
||||
orientationManager
|
||||
);
|
||||
|
||||
return new Camera2(
|
||||
logger,
|
||||
cameraConnection,
|
||||
sessionManager,
|
||||
textureManager,
|
||||
orientationManager,
|
||||
parametersProvider,
|
||||
capabilitiesOperator,
|
||||
previewStream,
|
||||
rendererParametersOperator,
|
||||
focusExecutor,
|
||||
exposureGatheringExecutor,
|
||||
captureExecutor,
|
||||
availableLensPositionsProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user