Compare commits

...

67 Commits

Author SHA1 Message Date
Dionysis 2513e42830 Notify exception from the callback instead of crashing the app. Fixes #164, #165 2018-01-03 02:37:49 +02:00
Dionysis 4ed72421aa Bump version 2018-01-03 01:59:40 +02:00
Dionysis dddd81600a Link frame processor in builder with FA 2018-01-03 01:57:46 +02:00
Dionysis Lorentzos 2c80a903c1 Merge pull request #160 from Fotoapparat/v2
🎄 V2 🎄
2017-12-31 18:07:07 +02:00
Dionysis 3f6ecbd37e Update readme and version 2017-12-31 17:18:32 +02:00
Dionysis d9114d4824 Clean 2017-12-31 17:08:38 +02:00
Dionysis 1768d73e59 Clean 2017-12-31 15:25:58 +02:00
Dionysis 51aa9eb9dc Revert compile + update versions 2017-12-31 15:19:27 +02:00
Dionysis 1cfbd2216f Bump gradle 2017-12-31 14:30:07 +02:00
Dionysis bde4c27f8b Remove ci warnings 2017-12-30 18:39:44 +02:00
Dionysis f4b60a445a Bump version 2017-12-30 17:59:14 +02:00
Dionysis 64186ec825 Use api in gradle 2017-12-30 17:58:43 +02:00
Dionysis 96f9add77d Bump coroutines version 2017-12-30 17:37:49 +02:00
Dionysis b6a6f56e38 Bump version 2017-12-30 16:58:59 +02:00
Dionysis a694830c1c Fix issue when Samsung phones have additional focus modes than the android api ones. 2017-12-30 16:58:28 +02:00
Dionysis 352a96a13c Tasks will be retroactive actions to the currently selected camera 2017-12-30 03:07:11 +02:00
Dionysis 33c5054150 Bump version 2017-12-24 21:11:26 +02:00
Dionysis ba02497fbd Update travis 2017-12-24 21:11:03 +02:00
Dionysis 95196e046b Shutdown tasks if we have a stop() operation of FA 2017-12-24 20:54:08 +02:00
Dionysis cb00abc810 Update tools 2017-12-24 17:41:38 +02:00
Dionysis 5e9a6afff7 Fix crash that SurfaceTexture is no longer available 2017-12-24 17:41:33 +02:00
Dionysis 75d20c2f37 Resolve todos 2017-12-20 12:59:15 +02:00
Dionysis d0da61bfb8 Update version 2017-12-20 12:50:47 +02:00
Dionysis 92f099500d Accept license if needed 2017-12-20 12:41:19 +02:00
Dionysis 15b4f35555 Remove 2017-12-19 18:03:47 +02:00
Dionysis c1b293da58 Ahhh travis... 2017-12-19 17:59:38 +02:00
Dionysis 09cca52111 Get latest android tools 2017-12-19 17:41:05 +02:00
Dionysis 9d6f4cb9e8 Try accept travis licenses 2017-12-19 16:51:25 +02:00
Dionysis 2ea813a257 Update build tools 2017-12-19 16:38:00 +02:00
Dionysis 8dbe53d261 Update travis file 2017-12-19 16:23:55 +02:00
Dionysis 4bd05b679f Improve sample 2017-12-19 01:00:35 +02:00
Dionysis 01dc60df24 Fix orientation crash 2017-12-19 00:41:49 +02:00
Dionysis 2b05601405 Improve sample app 2017-12-18 23:57:06 +02:00
Dionysis 1ca2462f07 Support anti banding mode 2017-12-18 19:21:41 +02:00
Dionysis e553699335 Merge branch 'master' into v2
# Conflicts:
#	README.md
#	fotoapparat/src/main/java/io/fotoapparat/Fotoapparat.java
#	fotoapparat/src/main/java/io/fotoapparat/FotoapparatBuilder.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Capabilities.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/CameraParametersDecorator.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/ParametersConverter.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/FocusCapability.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/Characteristics.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/Parameters.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/factory/ParametersFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/provider/InitialParametersProvider.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/update/UpdateRequest.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/parameter/UpdateParametersRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v1/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/Camera2Test.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/factory/ParametersFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/parameter/UpdateParametersRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/zoom/UpdateZoomLevelRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/task/GetCapabilitiesTaskTest.java
#	sample/src/main/java/io/fotoapparat/sample/MainActivity.java
2017-12-18 18:48:07 +02:00
Dionysis Lorentzos 1d6d2d0977 Merge pull request #158 from simone-gasparini/154_add_antibanding
Added anti banding parameter
2017-12-18 17:43:01 +01:00
Simone Gasparini 94268a339a Added anti banding parameter. Test fixes. 2017-12-18 13:51:10 +01:00
Simone Gasparini 03170f6b26 Added anti banding parameter 2017-12-18 13:06:04 +01:00
Dionysis fe719e8bf3 Fix tests 2017-12-18 13:45:26 +02:00
Dionysis d7db6fa196 Support jpeg quality 2017-12-18 02:54:19 +02:00
Dionysis 29cc23e047 Advertise kotlin 2017-12-17 18:44:10 +02:00
Dionysis 89a172dbb1 new line 2017-12-17 18:42:30 +02:00
Dionysis e4acda6aea Fix link 2017-12-17 18:41:57 +02:00
Dionysis 3fc55404df Update Readme 2017-12-17 18:41:04 +02:00
Dionysis 16fb6bd94c Update Readme 2017-12-17 18:39:26 +02:00
Dionysis df59b3996e Update Readme 2017-12-17 18:36:59 +02:00
Dionysis b8e43e836a Update Readme 2017-12-17 18:36:39 +02:00
Dionysis 6e15327f6c Update Readme 2017-12-17 18:35:44 +02:00
Dionysis 6aef37b3f8 Make file to kotlin 2017-12-17 17:52:59 +02:00
Dionysis 21e4b89bf7 Update adapters to kotlin + add flowable support 2017-12-17 17:51:47 +02:00
Dionysis e3f0aa7421 Rename tag 2017-12-17 16:47:54 +02:00
Dionysis 35062675d9 Implement can select camera 2017-12-14 00:07:54 +01:00
Dionysis aff03c17c8 Implement samples in both kt & java. Add builders for helping java users 2017-12-13 23:34:18 +01:00
Dionysis b4e6db0a04 Add fileLogger() in loggers 2017-12-13 21:09:28 +01:00
Dionysis 7517702165 Init FA v2 2017-12-11 22:39:00 +01:00
Dionysis 56d32eee60 Update build gradle 2017-12-11 20:58:08 +01:00
Dionysis 132736e4f7 Remove duplicate tests 2017-12-10 21:04:40 +01:00
Dionysis 2ac387d6ff Migrate build scripts 2017-12-10 20:51:57 +01:00
Dmitry Zaitsev f4ff8b91b9 Merge pull request #147 from ezaquarii/is-started
Added Fotoapparat.isStarted() getter
2017-12-07 07:03:38 +01:00
Krzysztof Narkiewicz 7b047501a6 Added Fotoapparat.isStarted() getter 2017-12-07 05:30:22 +00:00
Dionysis 80f43fcf63 Allow null fps range and sensor sensitivity (ISO) 2017-12-06 22:25:06 +01:00
Dionysis b7417650c6 Fix null check 2017-12-06 22:06:36 +01:00
Dmitry Zaitsev e7e264f522 Merge pull request #143 from Fotoapparat/prevent_wrong_selectors
Selectors cannot be trusted, therefore we ensure their values are correct
2017-12-04 16:22:20 +01:00
Dionysis e501d0d25f Add more parameter factory tests 2017-12-04 15:18:38 +01:00
Dionysis 39765cdb07 Fix/add parameter factory tests 2017-12-04 15:12:04 +01:00
Dionysis 71480f4fa8 Rename method 2017-12-04 14:52:37 +01:00
Dionysis a6028d56f3 Selectors cannot be trusted, therefore we ensure their values are correct 2017-12-02 14:21:11 +01:00
409 changed files with 8638 additions and 17410 deletions
+6 -2
View File
@@ -5,9 +5,13 @@ android:
components:
- tools
- platform-tools
- build-tools-25.0.2
- android-25
- tools
- build-tools-27.0.3
- android-27
- extra-android-m2repository
before_install:
- yes | sdkmanager "platforms;android-27"
script:
- ./gradlew build test
+81 -74
View File
@@ -12,22 +12,23 @@ What it provides:
- Simple yet powerful parameters customization.
- Standalone custom `CameraView` which can be integrated into any `Activity`.
- Fixes and workarounds for device-specific problems.
- Both Kotlin and Java friendly configurations.
- Last, but not least, non 0% test coverage.
Taking picture becomes as simple as:
```java
Fotoapparat fotoapparat = Fotoapparat
.with(context)
.into(cameraView)
.build();
fotoapparat.start();
```kotlin
val fotoapparat = Fotoapparat(
context = this,
view = cameraView
)
fotoapparat.start()
fotoapparat
.takePicture()
.saveToFile(someFile);
.saveToFile(someFile)
```
## How it works
@@ -45,48 +46,40 @@ Add `CameraView` to your layout
### Step Two
Configure `Fotoapparat` instance
Configure `Fotoapparat` instance.
```kotlin
Fotoapparat(
context = this,
view = cameraView, // view which will draw the camera preview
scaleType = ScaleType.CenterCrop, // (optional) we want the preview to fill the view
lensPosition = back(), // (optional) we want back camera
cameraConfiguration = configuration, // (optional) define an advanced configuration
logger = loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
),
cameraErrorCallback = { error -> } // (optional) log fatal errors
)
```
Check the [wiki for the `configuration` options e.g. change iso](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Kotlin)
Are you using Java only? See our [wiki for the java-friendly configuration](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Java).
```java
Fotoapparat
.with(context)
.into(cameraView) // view which will draw the camera preview
.previewScaleType(ScaleType.CENTER_CROP) // we want the preview to fill the view
.photoSize(biggestSize()) // we want to have the biggest photo possible
.lensPosition(back()) // we want back camera
.focusMode(firstAvailable( // (optional) use the first focus mode which is supported by device
continuousFocus(),
autoFocus(), // in case if continuous focus is not available on device, auto focus will be used
fixed() // if even auto focus is not available - fixed focus mode will be used
))
.flash(firstAvailable( // (optional) similar to how it is done for focus mode, this time for flash
autoRedEye(),
autoFlash(),
torch()
))
.frameProcessor(myFrameProcessor) // (optional) receives each frame from preview stream
.logger(loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
))
.build();
```
### Step Three
Call `start()` and `stop()`. No rocket science here.
```java
@Override
protected void onStart() {
super.onStart();
fotoapparat.start();
```kotlin
override fun onStart() {
super.onStart()
fotoapparat.start()
}
@Override
protected void onStop() {
super.onStop();
fotoapparat.stop();
override fun onStop() {
super.onStop()
fotoapparat.stop()
}
```
@@ -94,60 +87,74 @@ protected void onStop() {
Finally we are ready to take picture. You have various options.
```java
PhotoResult photoResult = fotoapparat.takePicture();
```kotlin
val photoResult = fotoapparat.takePicture()
// Asynchronously saves photo to file
photoResult.saveToFile(someFile);
photoResult.saveToFile(someFile)
// Asynchronously converts photo to bitmap and returns result on main thread
photoResult
.toBitmap()
.whenAvailable(new PendingResult.Callback<BitmapPhoto>() {
@Override
public void onResult(BitmapPhoto result) {
ImageView imageView = (ImageView) findViewById(R.id.result);
imageView.setImageBitmap(result.bitmap);
imageView.setRotation(-result.rotationDegrees);
}
});
.whenAvailable { bitmapPhoto ->
val imageView = (ImageView) findViewById(R.id.result)
imageView.setImageBitmap(bitmapPhoto.bitmap)
imageView.setRotation(-bitmapPhoto.rotationDegrees)
}
// Of course you can also get a photo in a blocking way. Do not do it on main thread though.
BitmapPhoto result = photoResult.toBitmap().await();
val result = photoResult.toBitmap().await()
// Convert asynchronous events to RxJava 1.x/2.x types. See /fotoapparat-adapters/ module
// Convert asynchronous events to RxJava 1.x/2.x types.
// See /fotoapparat-adapters/ module
photoResult
.toBitmap()
.adapt(SingleAdapter.<BitmapPhoto>toSingle())
.subscribe(bitmapPhoto -> {
.toSingle()
.subscribe { bitmapPhoto ->
});
}
```
## Update parameters
It is also possible to update some parameters after `Fotoapparat` was already started.
```java
fotoapparat.updateParameters(
UpdateRequest.builder()
.flash(
isTurnedOn
? torch()
: off()
)
.build()
```kotlin
fotoapparat.updateConfiguration(
UpdateConfiguration(
flashMode = if (isChecked) torch() else off()
// ...
// all the parameters available in CameraConfiguration
)
)
```
Or alternatively you may provide updates on an existing full configuration.
```kotlin
val configuration = CameraConfiguration(
// A full configuration
// ...
)
fotoapparat.updateConfiguration(
configuration.copy(
flashMode = if (isChecked) torch() else off()
// all the parameters available in CameraConfiguration
)
)
```
## Switch cameras
In order to switch between multiple instances of Fotoapparat, for example an instance using the device's front camera and another using the back, `FotoapparatSwitcher` can be used:
In order to switch between cameras, `Fotoapparat.switchTo()` can be used with the new desired `lensPosition` and its `cameraConfiguration`.
```java
FotoapparatSwitcher fotoapparatSwitcher = FotoapparatSwitcher.withDefault(fotoapparatFront);
fotoapparatSwitcher.switchTo(fotoapparatBack);
```kotlin
fotoapparat.switchTo(
lensPosition = front(),
cameraConfiguration = newConfigurationForFrontCamera
)
```
## Set up
@@ -159,7 +166,7 @@ repositories {
maven { url 'https://jitpack.io' }
}
compile 'io.fotoapparat.fotoapparat:library:1.5.0'
compile 'io.fotoapparat.fotoapparat:library:2.0.2'
```
Camera permission will be automatically added to your `AndroidManifest.xml`. Do not forget to request this permission on Marshmallow and higher.
+32 -2
View File
@@ -1,17 +1,47 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
versions = [
gradle : '4.4.1',
kotlin : '1.2.10',
code : 1,
name : '1.0.0',
sdk : [
minimum: 16,
target : 27
],
android: [
buildTools: '27.0.3',
support : '27.0.2'
],
rx : [
rxJava1: '1.2.9',
rxJava2: '2.0.8'
],
test : [
junit : '4.12',
mockito: '2.13.0'
],
util : [
commons: '2.5'
]
]
}
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:${project.androidBuildToolsVersion}"
classpath "com.android.tools.build:gradle:3.1.0-alpha06"
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
@@ -21,5 +51,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = project.gradleVersion
gradleVersion = versions.gradle
}
+21
View File
@@ -5,6 +5,18 @@ The child modules contained herein are additional adapters for other popular exe
To use, supply an instance of your desired adapter when performing a task of a PendingResult.
Kotlin:
```java
fotoapparat.takePicture()
.toBitmap()
.toObservable()
.subscribe { bitmapPhoto ->
// Do something with the photo
}
```
Java:
```java
fotoapparat.takePicture()
.toBitmap()
@@ -16,3 +28,12 @@ fotoapparat.takePicture()
}
});
```
Supported types:
* `Observable<T>` : RxJava 1/2
* `Flowable<T>` : RxJava 2
* `Single<T>` : RxJava 1/2
* `Completable` : RxJava 1/2
+11 -7
View File
@@ -1,23 +1,27 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'adapter-rxjava'
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex:rxjava:${project.rxjava1Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex:rxjava:${versions.rx.rxJava1}"
testCompile "junit:junit:${project.junitVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex:rxjava:${versions.rx.rxJava1}"
testImplementation "junit:junit:${versions.test.junit}"
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
fun <T> toCompletable(): Function1<Future<T>, Completable> {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.from(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.from(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.from(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.from(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
+1
View File
@@ -5,6 +5,7 @@ An `Adapter` for adapting RxJava 2.x types.
Available types:
* `Observable<T>`
* `Flowable<T>`
* `Single<T>`
* `Completable`
+11 -7
View File
@@ -1,23 +1,27 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'adapter-rxjava2'
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex.rxjava2:rxjava:${project.rxjava2Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
testCompile "junit:junit:${project.junitVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
testImplementation "junit:junit:${versions.test.junit}"
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,31 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <R> toCompletable(): (Future<R>) -> Completable {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Flowable
import java.util.concurrent.Future
/**
* Adapter for [Flowable].
*/
object FlowableAdapter {
/**
* @return Adapter which adapts result to [Flowable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toFlowable(): Function1<Future<T>, Flowable<T>> {
return { future -> Flowable.fromFuture(future) }
}
}
/**
* @return A [Flowable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toFlowable(): Flowable<T> {
return adapt { future -> Flowable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.fromFuture(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.fromFuture(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.fromFuture(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava2
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class CompletableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -0,0 +1,55 @@
package io.fotoapparat.result.adapter.rxjava2;
import org.junit.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import io.reactivex.subscribers.TestSubscriber;
public class FlowableAdapterTest {
private TestSubscriber<String> observer = new TestSubscriber<>();
@Test
public void completed() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello";
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertValue("Hello");
observer.assertNoErrors();
}
@Test
public void error() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("What a failure");
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertNoValues();
observer.assertError(ExecutionException.class);
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class ObservableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class SingleAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
+14 -8
View File
@@ -1,15 +1,18 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
kotlin.experimental.coroutines 'enable'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'library'
}
@@ -26,9 +29,12 @@ android {
}
dependencies {
compile "com.android.support:support-annotations:${project.appCompatVersion}"
compile "com.android.support:support-annotations:${versions.android.support}"
compile "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21'
testCompile "junit:junit:${project.junitVersion}"
testCompile "org.mockito:mockito-core:${project.mockitoVersion}"
testCompile 'commons-io:commons-io:2.5'
testImplementation "junit:junit:${versions.test.junit}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
testImplementation "org.mockito:mockito-core:${versions.test.mockito}"
testImplementation "commons-io:commons-io:${versions.util.commons}"
}
@@ -1,342 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.fotoapparat.error.Callbacks;
import io.fotoapparat.error.CameraErrorCallback;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.orientation.OrientationSensor;
import io.fotoapparat.hardware.orientation.RotationListener;
import io.fotoapparat.hardware.orientation.ScreenOrientationProvider;
import io.fotoapparat.parameter.provider.CapabilitiesProvider;
import io.fotoapparat.parameter.provider.CurrentParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersValidator;
import io.fotoapparat.parameter.update.UpdateRequest;
import io.fotoapparat.result.CapabilitiesResult;
import io.fotoapparat.result.FocusResult;
import io.fotoapparat.result.ParametersResult;
import io.fotoapparat.result.PendingResult;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.routine.CheckAvailabilityRoutine;
import io.fotoapparat.routine.ConfigurePreviewStreamRoutine;
import io.fotoapparat.routine.StartCameraRoutine;
import io.fotoapparat.routine.StopCameraRoutine;
import io.fotoapparat.routine.UpdateOrientationRoutine;
import io.fotoapparat.routine.focus.AutoFocusRoutine;
import io.fotoapparat.routine.parameter.UpdateParametersRoutine;
import io.fotoapparat.routine.picture.TakePictureRoutine;
import io.fotoapparat.routine.zoom.UpdateZoomLevelRoutine;
/**
* Camera. Takes pictures.
*/
public class Fotoapparat {
private static final Executor SERIAL_EXECUTOR = Executors.newSingleThreadExecutor();
private final StartCameraRoutine startCameraRoutine;
private final StopCameraRoutine stopCameraRoutine;
private final UpdateOrientationRoutine updateOrientationRoutine;
private final ConfigurePreviewStreamRoutine configurePreviewStreamRoutine;
private final CapabilitiesProvider capabilitiesProvider;
private final CurrentParametersProvider currentParametersProvider;
private final TakePictureRoutine takePictureRoutine;
private final AutoFocusRoutine autoFocusRoutine;
private final CheckAvailabilityRoutine checkAvailabilityRoutine;
private final UpdateParametersRoutine updateParametersRoutine;
private final UpdateZoomLevelRoutine updateZoomLevelRoutine;
private final Executor executor;
private boolean started = false;
Fotoapparat(StartCameraRoutine startCameraRoutine,
StopCameraRoutine stopCameraRoutine,
UpdateOrientationRoutine updateOrientationRoutine,
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine,
CapabilitiesProvider capabilitiesProvider,
CurrentParametersProvider parametersProvider,
TakePictureRoutine takePictureRoutine,
AutoFocusRoutine autoFocusRoutine,
CheckAvailabilityRoutine checkAvailabilityRoutine,
UpdateParametersRoutine updateParametersRoutine,
UpdateZoomLevelRoutine updateZoomLevelRoutine,
Executor executor) {
this.startCameraRoutine = startCameraRoutine;
this.stopCameraRoutine = stopCameraRoutine;
this.updateOrientationRoutine = updateOrientationRoutine;
this.configurePreviewStreamRoutine = configurePreviewStreamRoutine;
this.capabilitiesProvider = capabilitiesProvider;
this.currentParametersProvider = parametersProvider;
this.takePictureRoutine = takePictureRoutine;
this.autoFocusRoutine = autoFocusRoutine;
this.checkAvailabilityRoutine = checkAvailabilityRoutine;
this.updateParametersRoutine = updateParametersRoutine;
this.updateZoomLevelRoutine = updateZoomLevelRoutine;
this.executor = executor;
}
public static FotoapparatBuilder with(Context context) {
if (context == null) {
throw new IllegalStateException("Context is null.");
}
return new FotoapparatBuilder(context);
}
static Fotoapparat create(FotoapparatBuilder builder) {
CameraErrorCallback cameraErrorCallback = Callbacks.onMainThread(
builder.cameraErrorCallback
);
CameraDevice cameraDevice = builder.cameraProvider.get(builder.logger);
ScreenOrientationProvider screenOrientationProvider = new ScreenOrientationProvider(builder.context);
RotationListener rotationListener = new RotationListener(builder.context);
InitialParametersValidator parametersValidator = new InitialParametersValidator();
InitialParametersProvider initialParametersProvider = new InitialParametersProvider(
cameraDevice,
builder.photoSizeSelector,
builder.previewSizeSelector,
builder.focusModeSelector,
builder.flashSelector,
builder.previewFpsRangeSelector,
builder.sensorSensitivitySelector,
builder.jpegQuality,
parametersValidator
);
StartCameraRoutine startCameraRoutine = new StartCameraRoutine(
cameraDevice,
builder.renderer,
builder.scaleType,
builder.lensPositionSelector,
screenOrientationProvider,
initialParametersProvider,
cameraErrorCallback
);
StopCameraRoutine stopCameraRoutine = new StopCameraRoutine(cameraDevice);
OrientationSensor orientationSensor = new OrientationSensor(
rotationListener,
screenOrientationProvider
);
UpdateOrientationRoutine updateOrientationRoutine = new UpdateOrientationRoutine(
cameraDevice,
orientationSensor,
SERIAL_EXECUTOR,
builder.logger
);
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine = new ConfigurePreviewStreamRoutine(
cameraDevice,
builder.frameProcessor
);
CapabilitiesProvider capabilitiesProvider = new CapabilitiesProvider(
cameraDevice,
SERIAL_EXECUTOR
);
CurrentParametersProvider currentParametersProvider = new CurrentParametersProvider(
cameraDevice,
SERIAL_EXECUTOR
);
TakePictureRoutine takePictureRoutine = new TakePictureRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
AutoFocusRoutine autoFocusRoutine = new AutoFocusRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
CheckAvailabilityRoutine checkAvailabilityRoutine = new CheckAvailabilityRoutine(
cameraDevice,
builder.lensPositionSelector
);
UpdateParametersRoutine updateParametersRoutine = new UpdateParametersRoutine(
cameraDevice
);
UpdateZoomLevelRoutine updateZoomLevelRoutine = new UpdateZoomLevelRoutine(
cameraDevice
);
return new Fotoapparat(
startCameraRoutine,
stopCameraRoutine,
updateOrientationRoutine,
configurePreviewStreamRoutine,
capabilitiesProvider,
currentParametersProvider,
takePictureRoutine,
autoFocusRoutine,
checkAvailabilityRoutine,
updateParametersRoutine,
updateZoomLevelRoutine,
SERIAL_EXECUTOR
);
}
/**
* @return {@code true} if camera for this {@link Fotoapparat} is available. {@code false} if
* it is not available.
*/
public boolean isAvailable() {
return checkAvailabilityRoutine.isAvailable();
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return {@link CapabilitiesResult} which will deliver result asynchronously.
*/
public CapabilitiesResult getCapabilities() {
ensureStarted();
return capabilitiesProvider.getCapabilities();
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return {@link ParametersResult} which will deliver result asynchronously.
*/
public ParametersResult getCurrentParameters() {
ensureStarted();
return currentParametersProvider.getParameters();
}
/**
* Takes picture. Returns immediately.
*
* @return {@link PhotoResult} which will deliver result asynchronously.
*/
public PhotoResult takePicture() {
ensureStarted();
return takePictureRoutine.takePicture();
}
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*/
public Fotoapparat autoFocus() {
focus();
return this;
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*/
public PendingResult<FocusResult> focus() {
ensureStarted();
return autoFocusRoutine.autoFocus();
}
/**
* Asynchronously updates parameters of the camera. Must be called only after {@link #start()}.
*/
public void updateParameters(@NonNull final UpdateRequest updateRequest) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateParametersRoutine.updateParameters(updateRequest);
}
});
}
/**
* Asynchronously updates zoom level of the camera. Must be called only after {@link #start()}.
* <p>
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel zoom level of the camera. A value between 0 and 1.
*/
public void setZoom(@FloatRange(from = 0f, to = 1f) final float zoomLevel) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateZoomLevelRoutine.updateZoomLevel(zoomLevel);
}
});
}
/**
* Starts camera.
*
* @throws IllegalStateException if camera was already started.
*/
public void start() {
ensureNotStarted();
started = true;
startCamera();
configurePreviewStream();
updateOrientationRoutine.start();
}
/**
* Stops camera.
*
* @throws IllegalStateException if camera is not started.
*/
public void stop() {
ensureStarted();
started = false;
updateOrientationRoutine.stop();
stopCamera();
}
private void startCamera() {
executor.execute(
startCameraRoutine
);
}
private void stopCamera() {
executor.execute(
stopCameraRoutine
);
}
private void configurePreviewStream() {
executor.execute(
configurePreviewStreamRoutine
);
}
private void ensureStarted() {
if (!started) {
throw new IllegalStateException("Camera is not started!");
}
}
private void ensureNotStarted() {
if (started) {
throw new IllegalStateException("Camera is already started!");
}
}
}
@@ -0,0 +1,247 @@
package io.fotoapparat
import android.content.Context
import android.support.annotation.FloatRange
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.error.onMainThread
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.hardware.execute
import io.fotoapparat.hardware.executeTask
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.result.*
import io.fotoapparat.routine.camera.bootStart
import io.fotoapparat.routine.camera.shutDown
import io.fotoapparat.routine.camera.switchCamera
import io.fotoapparat.routine.camera.updateDeviceConfiguration
import io.fotoapparat.routine.capability.getCapabilities
import io.fotoapparat.routine.focus.focus
import io.fotoapparat.routine.parameter.getCurrentParameters
import io.fotoapparat.routine.photo.takePhoto
import io.fotoapparat.routine.zoom.updateZoomLevel
import io.fotoapparat.selector.back
import io.fotoapparat.selector.external
import io.fotoapparat.selector.firstAvailable
import io.fotoapparat.selector.front
import io.fotoapparat.view.CameraRenderer
import java.util.concurrent.FutureTask
/**
* Camera. Takes pictures.
*/
class Fotoapparat
@JvmOverloads constructor(
context: Context,
view: CameraRenderer,
lensPosition: Collection<LensPosition>.() -> LensPosition? = firstAvailable(
back(),
front(),
external()
),
scaleType: ScaleType = ScaleType.CenterCrop,
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
cameraErrorCallback: ((CameraException) -> Unit) = {},
private val logger: Logger = none()
) {
private val mainThreadErrorCallback = cameraErrorCallback.onMainThread()
private val display = Display(context)
private val device = Device(
cameraRenderer = view,
logger = logger,
display = display,
scaleType = scaleType,
initialLensPositionSelector = lensPosition,
initialConfiguration = cameraConfiguration
)
private val orientationSensor = OrientationSensor(
context = context,
device = device
)
init {
logger.recordMethod()
}
/**
* Starts camera.
*
* @throws IllegalStateException If the camera has already started.
*/
fun start() {
logger.recordMethod()
execute {
device.bootStart(
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
}
}
/**
* Stops camera.
*
* @throws IllegalStateException If the camera has not started.
*/
fun stop() {
logger.recordMethod()
execute {
device.shutDown(
orientationSensor = orientationSensor
)
}
}
/**
* Takes picture, returns immediately.
*
* @return [PhotoResult] which will deliver result asynchronously.
*/
fun takePicture(): PhotoResult {
logger.recordMethod()
val takePictureTask = FutureTask<Photo> {
device.takePhoto()
}
executeTask(takePictureTask)
return PhotoResult.fromFuture(takePictureTask, logger)
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return [CapabilitiesResult] which will deliver result asynchronously.
*/
fun getCapabilities(): CapabilitiesResult {
logger.recordMethod()
val getCapabilitiesTask = FutureTask<Capabilities> {
device.getCapabilities()
}
executeTask(getCapabilitiesTask)
return PendingResult.fromFuture(getCapabilitiesTask, logger)
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return [ParametersResult] which will deliver result asynchronously.
*/
fun getCurrentParameters(): ParametersResult {
logger.recordMethod()
val getCameraParametersTask = FutureTask<CameraParameters> {
device.getCurrentParameters()
}
executeTask(getCameraParametersTask)
return PendingResult.fromFuture(getCameraParametersTask, logger)
}
/**
* Updates current configuration.
*
* @throws IllegalStateException If the current camera has not started.
*/
fun updateConfiguration(newConfiguration: Configuration) = execute {
logger.recordMethod()
device.updateDeviceConfiguration(newConfiguration)
}
/**
* Asynchronously updates zoom level of the camera.
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel Zoom level of the camera. A value between 0 and 1.
* @throws IllegalStateException If the current camera has not started.
*/
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = execute {
logger.recordMethod()
device.updateZoomLevel(
zoomLevel = zoomLevel
)
}
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*
* @see Fotoapparat.focus
*/
fun autoFocus(): Fotoapparat {
logger.recordMethod()
focus()
return this
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*
* @see Fotoapparat.autoFocus
*/
fun focus(): PendingResult<FocusResult> {
logger.recordMethod()
val focusTask = FutureTask<FocusResult> {
device.focus()
}
executeTask(focusTask)
return PendingResult.fromFuture(focusTask, logger)
}
/**
* Switches to another camera. If previous camera has already started then it will be
* stopped automatically and new will start.
*/
fun switchTo(
lensPosition: Collection<LensPosition>.() -> LensPosition?,
cameraConfiguration: CameraConfiguration
) {
logger.recordMethod()
execute {
device.switchCamera(
newLensPositionSelector = lensPosition,
newConfiguration = cameraConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
)
}
}
/**
* @return `true` if selected lens position is available. `false` if it is not available.
*/
fun isAvailable(
selector: (Collection<LensPosition>) -> LensPosition?
): Boolean {
return device.canSelectCamera(selector)
}
companion object {
@JvmStatic
fun with(context: Context) = FotoapparatBuilder(context)
}
}
@@ -1,220 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import io.fotoapparat.error.CameraErrorCallback;
import java.util.Collection;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.provider.CameraProvider;
import io.fotoapparat.log.Logger;
import io.fotoapparat.log.Loggers;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.ScaleType;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.selector.FlashSelectors;
import io.fotoapparat.parameter.selector.SelectorFunction;
import io.fotoapparat.parameter.selector.Selectors;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.view.CameraRenderer;
import io.fotoapparat.view.CameraView;
import static io.fotoapparat.hardware.provider.CameraProviders.v1;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.autoFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.continuousFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.fixed;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.back;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.external;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.front;
import static io.fotoapparat.parameter.selector.Selectors.firstAvailable;
import static io.fotoapparat.parameter.selector.SizeSelectors.biggestSize;
/**
* Builder for {@link Fotoapparat}.
*/
public class FotoapparatBuilder {
Context context;
CameraProvider cameraProvider = v1();
CameraRenderer renderer;
SelectorFunction<Collection<LensPosition>, LensPosition> lensPositionSelector = firstAvailable(
back(),
front(),
external()
);
SelectorFunction<Collection<Size>, Size> photoSizeSelector = biggestSize();
SelectorFunction<Collection<Size>, Size> previewSizeSelector = biggestSize();
SelectorFunction<Collection<FocusMode>, FocusMode> focusModeSelector = firstAvailable(
continuousFocus(),
autoFocus(),
fixed()
);
SelectorFunction<Collection<Flash>, Flash> flashSelector = FlashSelectors.off();
SelectorFunction<Collection<Range<Integer>>, Range<Integer>> previewFpsRangeSelector = Selectors.nothing();
SelectorFunction<Range<Integer>, Integer> sensorSensitivitySelector = Selectors.nothing();
Integer jpegQuality;
ScaleType scaleType = ScaleType.CENTER_CROP;
FrameProcessor frameProcessor = null;
Logger logger = Loggers.none();
CameraErrorCallback cameraErrorCallback = CameraErrorCallback.NULL;
FotoapparatBuilder(@NonNull Context context) {
this.context = context;
}
/**
* @param cameraProvider decides which {@link CameraDevice} to use.
*/
public FotoapparatBuilder cameraProvider(@NonNull CameraProvider cameraProvider) {
this.cameraProvider = cameraProvider;
return this;
}
/**
* @param selector selects size of the photo (in pixels) from list of available sizes.
*/
public FotoapparatBuilder photoSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
photoSizeSelector = selector;
return this;
}
/**
* @param selector selects size of preview stream (in pixels) from list of available sizes.
*/
public FotoapparatBuilder previewSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
previewSizeSelector = selector;
return this;
}
/**
* @param scaleType of preview inside the view.
*/
public FotoapparatBuilder previewScaleType(ScaleType scaleType) {
this.scaleType = scaleType;
return this;
}
/**
* @param selector selects focus mode from list of available modes.
*/
public FotoapparatBuilder focusMode(@NonNull SelectorFunction<Collection<FocusMode>, FocusMode> selector) {
focusModeSelector = selector;
return this;
}
/**
* @param selector selects flash mode from list of available modes.
*/
public FotoapparatBuilder flash(@NonNull SelectorFunction<Collection<Flash>, Flash> selector) {
flashSelector = selector;
return this;
}
/**
* @param selector camera sensor position from list of available positions.
*/
public FotoapparatBuilder lensPosition(@NonNull SelectorFunction<Collection<LensPosition>, LensPosition> selector) {
lensPositionSelector = selector;
return this;
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
public FotoapparatBuilder previewFpsRange(@NonNull SelectorFunction<Collection<Range<Integer>>, Range<Integer>> selector) {
previewFpsRangeSelector = selector;
return this;
}
/**
* @param selector selects ISO value from range of available values.
*/
public FotoapparatBuilder sensorSensitivity(@NonNull SelectorFunction<Range<Integer>, Integer> selector) {
sensorSensitivitySelector = selector;
return this;
}
/**
* @param jpegQuality of the picture (1-100)
*/
public FotoapparatBuilder jpegQuality(@IntRange(from=0,to=100) @NonNull Integer jpegQuality) {
this.jpegQuality = jpegQuality;
return this;
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
public FotoapparatBuilder frameProcessor(@NonNull FrameProcessor frameProcessor) {
this.frameProcessor = frameProcessor;
return this;
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see Loggers
*/
public FotoapparatBuilder logger(@NonNull Logger logger) {
this.logger = logger;
return this;
}
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorCallback
*/
public FotoapparatBuilder cameraErrorCallback(@NonNull CameraErrorCallback callback) {
this.cameraErrorCallback = callback;
return this;
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
public FotoapparatBuilder into(@NonNull CameraRenderer renderer) {
this.renderer = renderer;
return this;
}
/**
* @return set up instance of {@link Fotoapparat}.
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
public Fotoapparat build() {
validate();
return Fotoapparat.create(this);
}
private void validate() {
if (cameraProvider == null) {
throw new IllegalStateException("CameraProvider is mandatory.");
}
if (renderer == null) {
throw new IllegalStateException("CameraRenderer is mandatory.");
}
if (lensPositionSelector == null) {
throw new IllegalStateException("LensPosition selector is mandatory.");
}
if (photoSizeSelector == null) {
throw new IllegalStateException("Photo size selector is mandatory.");
}
}
}
@@ -0,0 +1,186 @@
package io.fotoapparat
import android.content.Context
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
import io.fotoapparat.selector.back
import io.fotoapparat.selector.external
import io.fotoapparat.selector.firstAvailable
import io.fotoapparat.selector.front
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.CameraView
/**
* Builder for [Fotoapparat].
*/
class FotoapparatBuilder internal constructor(private var context: Context) {
internal var lensPositionSelector: Iterable<LensPosition>.() -> LensPosition? = firstAvailable(
back(),
front(),
external()
)
internal var cameraErrorCallback: (CameraException) -> Unit = {}
internal var renderer: CameraRenderer? = null
internal var scaleType: ScaleType = ScaleType.CenterCrop
internal var logger: Logger = none()
internal var configuration = CameraConfiguration.default()
/**
* @param selector camera sensor position from list of available positions.
*/
fun lensPosition(selector: Iterable<LensPosition>.() -> LensPosition?): FotoapparatBuilder {
lensPositionSelector = selector
return this
}
/**
* @param scaleType of preview inside the view.
*/
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder {
this.scaleType = scaleType
return this
}
/**
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
*/
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
configuration = configuration.copy(
pictureResolution = selector
)
return this
}
/**
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
*/
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
configuration = configuration.copy(
previewResolution = selector
)
return this
}
/**
* @param selector selects focus mode from list of available modes.
*/
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): FotoapparatBuilder {
configuration = configuration.copy(
focusMode = selector
)
return this
}
/**
* @param selector selects flash mode from list of available modes.
*/
fun flash(selector: Iterable<Flash>.() -> Flash?): FotoapparatBuilder {
configuration = configuration.copy(
flashMode = selector
)
return this
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): FotoapparatBuilder {
configuration = configuration.copy(
previewFpsRange = selector
)
return this
}
/**
* @param selector selects ISO value from range of available values.
*/
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): FotoapparatBuilder {
configuration = configuration.copy(
sensorSensitivity = selector
)
return this
}
/**
* @param selector of the Jpeg picture quality.
*/
fun jpegQuality(selector: IntRange.() -> Int?): FotoapparatBuilder {
configuration = configuration.copy(
jpegQuality = selector
)
return this
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
fun frameProcessor(frameProcessor: (Frame) -> Unit): FotoapparatBuilder {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
return this
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see io.fotoapparat.log.Loggers
*/
fun logger(logger: Logger): FotoapparatBuilder {
this.logger = logger
return this
}
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorCallback
*/
fun cameraErrorCallback(callback: (CameraException) -> Unit): FotoapparatBuilder {
cameraErrorCallback = callback
return this
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
fun into(renderer: CameraRenderer): FotoapparatBuilder {
this.renderer = renderer
return this
}
/**
* @return set up instance of [Fotoapparat].
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
fun build() = buildInternal(
renderer = renderer
)
private fun buildInternal(
renderer: CameraRenderer?
): Fotoapparat {
if (renderer == null) {
throw IllegalStateException("CameraRenderer is mandatory.")
}
return Fotoapparat(
context = context,
view = renderer,
lensPosition = lensPositionSelector,
cameraConfiguration = configuration,
scaleType = scaleType,
cameraErrorCallback = cameraErrorCallback,
logger = logger
)
}
}
@@ -1,77 +0,0 @@
package io.fotoapparat;
import android.support.annotation.NonNull;
/**
* Switches between different instances of {@link Fotoapparat}. Convenient when you want to allow
* user to switch between different cameras or configurations.
* <p>
* This class is not thread safe. Consider using it from a single thread.
*/
public class FotoapparatSwitcher {
@NonNull
private Fotoapparat fotoapparat;
private boolean started = false;
private FotoapparatSwitcher(@NonNull Fotoapparat fotoapparat) {
this.fotoapparat = fotoapparat;
}
/**
* @return {@link FotoapparatSwitcher} with given {@link Fotoapparat} used by default.
*/
public static FotoapparatSwitcher withDefault(@NonNull Fotoapparat fotoapparat) {
return new FotoapparatSwitcher(fotoapparat);
}
/**
* Starts {@link Fotoapparat} associated with this switcher. Every new {@link Fotoapparat} will
* be started automatically until {@link #stop()} is called.
*
* @throws IllegalStateException if switcher is already started.
*/
public void start() {
fotoapparat.start();
started = true;
}
/**
* Stops currently used {@link Fotoapparat}.
*
* @throws IllegalStateException if switcher is already stopped.
*/
public void stop() {
fotoapparat.stop();
started = false;
}
/**
* Switches to another {@link Fotoapparat}. If switcher is already started then previously used
* {@link Fotoapparat} will be stopped automatically and new {@link Fotoapparat} will be
* started.
*
* @param fotoapparat new {@link Fotoapparat} to use.
* @throws NullPointerException if given {@link Fotoapparat} is {@code null}.
*/
public void switchTo(@NonNull Fotoapparat fotoapparat) {
if (started) {
this.fotoapparat.stop();
fotoapparat.start();
}
this.fotoapparat = fotoapparat;
}
/**
* @return currently used instance of {@link Fotoapparat}.
*/
@NonNull
public Fotoapparat getCurrentFotoapparat() {
return fotoapparat;
}
}
@@ -0,0 +1,53 @@
package io.fotoapparat.capability
import io.fotoapparat.parameter.*
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.util.wrap
/**
* Capabilities of camera hardware.
*
* Sensor sensitivities is not guaranteed to always contain a value.
*/
data class Capabilities(
val canZoom: Boolean,
val flashModes: Set<Flash>,
val focusModes: Set<FocusMode>,
val canSmoothZoom: Boolean,
val jpegQualityRange: IntRange,
val previewFpsRanges: Set<FpsRange>,
val antiBandingModes: Set<AntiBandingMode>,
val pictureResolutions: Set<Resolution>,
val previewResolutions: Set<Resolution>,
val sensorSensitivities: Set<Int>
) {
init {
flashModes.ensureNotEmpty()
focusModes.ensureNotEmpty()
antiBandingModes.ensureNotEmpty()
previewFpsRanges.ensureNotEmpty()
pictureResolutions.ensureNotEmpty()
previewResolutions.ensureNotEmpty()
}
override fun toString(): String {
return "Capabilities" + lineSeparator +
"canZoom:" + canZoom.wrap() +
"flashModes:" + flashModes.wrap() +
"focusModes:" + focusModes.wrap() +
"canSmoothZoom:" + canSmoothZoom.wrap() +
"jpegQualityRange:" + jpegQualityRange.wrap() +
"antiBandingModes:" + antiBandingModes.wrap() +
"previewFpsRanges:" + previewFpsRanges.wrap() +
"pictureResolutions:" + pictureResolutions.wrap() +
"previewResolutions:" + previewResolutions.wrap() +
"sensorSensitivities:" + sensorSensitivities.wrap()
}
}
private inline fun <reified E> Set<E>.ensureNotEmpty() {
if (isEmpty()) {
throw IllegalArgumentException("Capabilities cannot have an empty Set<${E::class.java.simpleName}>.")
}
}
@@ -0,0 +1,32 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.capability.provide
import android.hardware.Camera
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.parameter.SupportedParameters
import io.fotoapparat.parameter.camera.convert.*
/**
* Returns the [io.fotoapparat.capability.Capabilities] of the given [Camera].
*/
internal fun Camera.getCapabilities() = SupportedParameters(parameters).getCapabilities()
private fun SupportedParameters.getCapabilities(): Capabilities {
return Capabilities(
canZoom = supportedZoom,
flashModes = flashModes.extract { it.toFlash() },
focusModes = focusModes.extract { it.toFocusMode() },
canSmoothZoom = supportedSmoothZoom,
jpegQualityRange = jpegQualityRange,
antiBandingModes = supportedAutoBandingModes.extract { it.toAntiBandingMode() },
sensorSensitivities = sensorSensitivities.toSet(),
previewFpsRanges = supportedPreviewFpsRanges.extract { it.toFpsRange() },
pictureResolutions = pictureResolutions.mapSizes(),
previewResolutions = previewResolutions.mapSizes()
)
}
private fun <Parameter : Any, Code> List<Code>.extract(converter: (Code) -> Parameter?) = mapNotNull { converter(it) }.toSet()
private fun Collection<Camera.Size>.mapSizes() = map { it.toResolution() }.toSet()
@@ -0,0 +1,18 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
/**
* Returns the [Characteristics] for the given `cameraId`.
*/
internal fun getCharacteristics(cameraId: Int): Characteristics {
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
return Characteristics(
cameraId,
info.facing.toLensPosition(),
info.orientation
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.characteristic
/**
* A camera characteristic.
*/
interface Characteristic
@@ -0,0 +1,10 @@
package io.fotoapparat.characteristic
/**
* A set of information about the camera.
*/
internal data class Characteristics(
val cameraId: Int,
val lensPosition: LensPosition,
val orientation: Int
)
@@ -1,23 +1,23 @@
package io.fotoapparat.parameter;
package io.fotoapparat.characteristic
/**
* The camera position relatively to the screen of the device.
*/
public enum LensPosition {
sealed class LensPosition : Characteristic {
/**
* The back camera.
*/
BACK,
object Back : LensPosition()
/**
* The front camera.
*/
FRONT,
object Front : LensPosition()
/**
* An external camera.
*/
EXTERNAL
object External : LensPosition()
}
}
@@ -0,0 +1,35 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
import io.fotoapparat.exception.camera.CameraException
/**
* Maps between [LensPosition] and Camera v1 lens position code id.
*
* @receiver Camera facing info id.
* @return [LensPosition] from the given lens position code id.
* `null` if position code id is not supported.
*/
internal fun Int.toLensPosition(): LensPosition {
return when (this) {
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
else -> throw IllegalArgumentException("Lens position $this is not supported.")
}
}
/**
* Maps between [LensPosition] and Camera v1 code id.
*
* @receiver [LensPosition]
* @return code of the camera as in [Camera.CameraInfo].
*/
fun LensPosition.toCameraId(): Int {
return (0 until Camera.getNumberOfCameras())
.find {
this == getCharacteristics(it).lensPosition
}
?: throw CameraException("Device has no camera for the desired lens position(s).")
}
@@ -0,0 +1,132 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
import io.fotoapparat.preview.FrameProcessor
import io.fotoapparat.selector.*
private const val DEFAULT_JPEG_QUALITY = 90
/**
* A camera configuration which has all it's selectors defined.
*/
data class CameraConfiguration(
override val flashMode: Iterable<Flash>.() -> Flash? = off(),
override val focusMode: Iterable<FocusMode>.() -> FocusMode? = firstAvailable(
continuousFocusPicture(),
autoFocus(),
fixed()
),
override val jpegQuality: (IntRange) -> Int? = manualJpegQuality(DEFAULT_JPEG_QUALITY),
override val frameProcessor: (Frame) -> Unit = {},
override val previewFpsRange: Iterable<FpsRange>.() -> FpsRange? = highestFps(),
override val antiBandingMode: Iterable<AntiBandingMode>.() -> AntiBandingMode? = firstAvailable(
auto(),
hz50(),
hz60(),
none()
),
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
override val pictureResolution: Iterable<Resolution>.() -> Resolution? = highestResolution(),
override val previewResolution: Iterable<Resolution>.() -> Resolution? = highestResolution()
) : Configuration {
/**
* Builder for [CameraConfiguration].
*/
class Builder internal constructor() {
private var cameraConfiguration: CameraConfiguration = default()
fun flash(selector: (Iterable<Flash>.() -> Flash?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
flashMode = selector
)
return this
}
fun focusMode(selector: (Iterable<FocusMode>.() -> FocusMode?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
focusMode = selector
)
return this
}
fun previewFpsRange(selector: (Iterable<FpsRange>.() -> FpsRange?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
previewFpsRange = selector
)
return this
}
fun sensorSensitivity(selector: (Iterable<Int>.() -> Int?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
)
return this
}
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
cameraConfiguration.copy(
antiBandingMode = selector
)
return this
}
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
cameraConfiguration.copy(
jpegQuality = selector
)
return this
}
fun previewResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
previewResolution = selector
)
return this
}
fun photoResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
cameraConfiguration = cameraConfiguration.copy(
pictureResolution = selector
)
return this
}
fun frameProcessor(frameProcessor: FrameProcessor): Builder {
cameraConfiguration = cameraConfiguration.copy(
frameProcessor = { frameProcessor.process(it) }
)
return this
}
/**
* Builds a new [CameraConfiguration].
*/
fun build(): CameraConfiguration = cameraConfiguration
}
companion object {
/**
* Alias for [CameraConfiguration.default]
*/
@JvmStatic
fun standard() = default()
/**
* Default [CameraConfiguration].
*/
@JvmStatic
fun default() = CameraConfiguration()
/**
* Creates a new [CameraConfiguration.Builder].
*/
@JvmStatic
fun builder() = CameraConfiguration.Builder()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
interface Configuration {
val flashMode: (Iterable<Flash>.() -> Flash?)?
val focusMode: (Iterable<FocusMode>.() -> FocusMode?)?
val jpegQuality: ((IntRange) -> Int?)?
val frameProcessor: ((Frame) -> Unit)?
val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)?
val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)?
val sensorSensitivity: (Iterable<Int>.() -> Int?)?
val previewResolution: (Iterable<Resolution>.() -> Resolution?)?
val pictureResolution: (Iterable<Resolution>.() -> Resolution?)?
}
@@ -0,0 +1,105 @@
package io.fotoapparat.configuration
import io.fotoapparat.parameter.*
import io.fotoapparat.preview.Frame
/**
* A camera update configuration.
*/
data class UpdateConfiguration(
override val flashMode: (Iterable<Flash>.() -> Flash?)? = null,
override val focusMode: (Iterable<FocusMode>.() -> FocusMode?)? = null,
override val jpegQuality: ((IntRange) -> Int?)? = null,
override val frameProcessor: ((Frame) -> Unit)? = null,
override val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)? = null,
override val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)? = null,
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
override val previewResolution: (Iterable<Resolution>.() -> Resolution?)? = null,
override val pictureResolution: (Iterable<Resolution>.() -> Resolution?)? = null
) : Configuration {
/**
* Builder for [UpdateConfiguration].
*/
class Builder internal constructor() {
private var configuration = UpdateConfiguration()
fun flash(selector: Iterable<Flash>.() -> Flash?): Builder {
configuration.copy(
flashMode = selector
)
return this
}
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): Builder {
configuration.copy(
focusMode = selector
)
return this
}
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): Builder {
configuration.copy(
previewFpsRange = selector
)
return this
}
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): Builder {
configuration.copy(
sensorSensitivity = selector
)
return this
}
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
configuration.copy(
antiBandingMode = selector
)
return this
}
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
configuration.copy(
jpegQuality = selector
)
return this
}
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
configuration.copy(
previewResolution = selector
)
return this
}
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
configuration.copy(
pictureResolution = selector
)
return this
}
fun frameProcessor(frameProcessor: (Frame) -> Unit): Builder {
configuration.copy(
frameProcessor = frameProcessor
)
return this
}
/**
* Builds a new [UpdateConfiguration].
*/
fun build(): UpdateConfiguration = configuration
}
companion object {
/**
* Creates a new [UpdateConfiguration.Builder].
*/
@JvmStatic
fun builder() = UpdateConfiguration.Builder()
}
}
@@ -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
)
@@ -0,0 +1,30 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a selected [Parameter] is not in the supported set.
*
* e.g. Camera supports flash `on`, `off` & `auto` and you ask for a `tomato`.
*/
internal class InvalidConfigurationException : CameraException {
constructor(
value: Any,
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters: $supportedParameters"
)
constructor(
value: Any,
klass: Class<out Comparable<*>>,
supportedRange: ClosedRange<*>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,9 @@
package io.fotoapparat.exception.camera
/**
* Thrown when the preview surface didn't become available.
*/
class UnavailableSurfaceException : CameraException(
"No preview surface became available before CameraView got detached from window. " +
"Camera didn't start. You may ignore this exception."
)
@@ -0,0 +1,25 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a configuration selector couldn't select a value.
*/
internal class UnsupportedConfigurationException : CameraException {
constructor(
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector couldn't select a value. " +
"Supported parameters: $supportedParameters"
)
constructor(
configurationName: String,
supportedRange: ClosedRange<*>
) : super(
"$configurationName configuration selector couldn't select a value. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception.camera
/**
* Thrown to indicate that the device has no camera for the desired lens position(s).
*/
class UnsupportedLensException : CameraException("Device has no camera for the desired lens position(s).")
@@ -0,0 +1,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,83 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.FloatRange;
import java.util.List;
import io.fotoapparat.hardware.operators.AutoFocusOperator;
import io.fotoapparat.hardware.operators.CapabilitiesOperator;
import io.fotoapparat.hardware.operators.CaptureOperator;
import io.fotoapparat.hardware.operators.ConnectionOperator;
import io.fotoapparat.hardware.operators.ExposureMeasurementOperator;
import io.fotoapparat.hardware.operators.OrientationOperator;
import io.fotoapparat.hardware.operators.ParametersOperator;
import io.fotoapparat.hardware.operators.PreviewOperator;
import io.fotoapparat.hardware.operators.PreviewStreamOperator;
import io.fotoapparat.hardware.operators.RendererParametersOperator;
import io.fotoapparat.hardware.operators.SurfaceOperator;
import io.fotoapparat.hardware.operators.ZoomOperator;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.lens.FocusResult;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
/**
* Abstraction for camera hardware.
*/
public interface CameraDevice extends CaptureOperator,
PreviewOperator, CapabilitiesOperator, OrientationOperator, ParametersOperator,
ConnectionOperator, SurfaceOperator, PreviewStreamOperator, RendererParametersOperator,
ExposureMeasurementOperator, AutoFocusOperator, AvailableLensPositionsProvider,
ZoomOperator {
@Override
void open(LensPosition lensPosition);
@Override
void close();
@Override
void startPreview();
@Override
void stopPreview();
@Override
void setDisplaySurface(Object displaySurface);
@Override
void setDisplayOrientation(int degrees);
@Override
void updateParameters(Parameters parameters);
@Override
Capabilities getCapabilities();
@Override
FocusResult autoFocus();
@Override
void measureExposure();
@Override
Photo takePicture();
@Override
PreviewStream getPreviewStream();
@Override
RendererParameters getRendererParameters();
@Override
List<LensPosition> getAvailableLensPositions();
@Override
void setZoom(@FloatRange(from = 0f, to = 1f) float level);
Parameters getCurrentParameters();
}
@@ -0,0 +1,380 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import android.media.MediaRecorder
import android.support.annotation.FloatRange
import android.view.Surface
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.capability.provide.getCapabilities
import io.fotoapparat.characteristic.Characteristics
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.characteristic.toCameraId
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.orientation.computeDisplayOrientation
import io.fotoapparat.hardware.orientation.computeImageOrientation
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.apply.applyNewParameters
import io.fotoapparat.preview.Frame
import io.fotoapparat.preview.PreviewStream
import io.fotoapparat.result.FocusResult
import io.fotoapparat.result.Photo
import io.fotoapparat.view.Preview
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
typealias PreviewSize = io.fotoapparat.parameter.Resolution
/**
* Camera.
*/
internal open class CameraDevice(
private val logger: Logger,
val characteristics: Characteristics
) {
private lateinit var cameraParameters: CameraParameters
private lateinit var previewStream: PreviewStream
private lateinit var capabilities: Capabilities
private lateinit var surface: Surface
private lateinit var camera: Camera
private var cachedZoomParameters: Camera.Parameters? = null
private var displayRotation = 0
var imageRotation = 0
/**
* Opens a connection to a camera.
*/
open fun open() {
logger.recordMethod()
val lensPosition = characteristics.lensPosition
val cameraId = lensPosition.toCameraId()
try {
camera = Camera.open(cameraId)
capabilities = camera.getCapabilities()
previewStream = PreviewStream(camera)
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to open camera with lens position: $lensPosition and id: $cameraId",
cause = e
)
}
}
/**
* Closes the connection to a camera.
*/
open fun close() {
logger.recordMethod()
camera.release()
}
/**
* Starts preview.
*/
open fun startPreview() {
logger.recordMethod()
try {
camera.startPreview()
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to start preview for camera with lens " +
"position: ${characteristics.lensPosition} and id: ${characteristics.cameraId}",
cause = e
)
}
}
/**
* Stops preview.
*/
open fun stopPreview() {
logger.recordMethod()
camera.stopPreview()
}
/**
* Unlock camera.
*/
open fun unlock() {
logger.recordMethod()
camera.unlock()
}
/**
* Lock camera.
*/
open fun lock() {
logger.recordMethod()
camera.lock()
}
/**
* Invokes a still photo capture action.
*
* @return The captured photo.
*/
open fun takePhoto(): Photo {
logger.recordMethod()
return camera.takePhoto(imageRotation)
}
/**
* Returns the [Capabilities] of the camera.
*/
open fun getCapabilities(): Capabilities {
logger.recordMethod()
return capabilities
}
/**
* Returns the [CameraParameters] used.
*/
open fun getParameters(): CameraParameters {
logger.recordMethod()
return cameraParameters
}
/**
* Updates the desired camera parameters.
*/
open fun updateParameters(cameraParameters: CameraParameters) {
logger.recordMethod()
this.cameraParameters = cameraParameters
logger.log("New camera parameters are: $cameraParameters")
camera.updateParameters(cameraParameters)
}
/**
* Updates the frame processor.
*/
open fun updateFrameProcessor(frameProcessor: ((Frame) -> Unit)?) {
logger.recordMethod()
previewStream.updateProcessorSafely(frameProcessor)
}
/**
* Sets the current orientation of the display.
*/
open fun setDisplayOrientation(degrees: Int) {
logger.recordMethod()
imageRotation = computeImageOrientation(
degrees = degrees,
characteristics = characteristics
)
displayRotation = computeDisplayOrientation(
degrees = degrees,
characteristics = characteristics
)
logger.log("Image Rotation is: $imageRotation. Display rotation is: $displayRotation")
previewStream.frameOrientation = imageRotation
camera.setDisplayOrientation(displayRotation)
}
/**
* Changes zoom level of the camera. Must be called only if zoom is supported.
*
* @param level normalized zoom level. Value in range [0..1].
*/
open fun setZoom(@FloatRange(from = 0.0, to = 1.0) level: Float) {
logger.recordMethod()
setZoomSafely(level)
}
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
open fun autoFocus(): FocusResult {
logger.recordMethod()
return camera.focusSafely()
}
/**
* Sets the desired surface on which the camera's preview will be displayed.
*/
open fun setDisplaySurface(preview: Preview) {
logger.recordMethod()
surface = camera.setDisplaySurface(preview)
}
/**
* Attaches the camera to the [MediaRecorder].
*/
open fun attachRecordingCamera(mediaRecorder: MediaRecorder) {
logger.recordMethod()
mediaRecorder.setCamera(camera)
}
/**
* Returns the [Resolution] of the displayed preview.
*/
open fun getPreviewResolution(): Resolution {
logger.recordMethod()
val previewResolution = camera.getPreviewResolution(imageRotation)
logger.log("Preview resolution is: $previewResolution")
return previewResolution
}
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
try {
setZoomUnsafe(level)
} catch (e: Exception) {
logger.log("Unable to change zoom level to " + level + " e: " + e.message)
}
}
private fun setZoomUnsafe(@FloatRange(from = 0.0, to = 1.0) level: Float) {
(cachedZoomParameters ?: camera.parameters)
.apply {
zoom = (maxZoom * level).toInt()
}
.let {
cachedZoomParameters = it
camera.parameters = it
}
}
private fun Camera.focusSafely(): FocusResult {
val latch = CountDownLatch(1)
try {
autoFocus { _, _ -> latch.countDown() }
} catch (e: Exception) {
logger.log("Failed to perform autofocus using device ${characteristics.cameraId} e: ${e.message}")
return FocusResult.UnableToFocus
}
try {
latch.await(AUTOFOCUS_TIMEOUT_SECONDS, TimeUnit.SECONDS)
} catch (e: InterruptedException) {
// Do nothing
}
return FocusResult.Focused
}
}
private const val AUTOFOCUS_TIMEOUT_SECONDS = 3L
private fun Camera.takePhoto(imageRotation: Int): Photo {
val latch = CountDownLatch(1)
val photoReference = AtomicReference<Photo>()
takePicture(
null,
null,
null,
Camera.PictureCallback { data, _ ->
photoReference.set(
Photo(data, imageRotation)
)
latch.countDown()
}
)
latch.await()
return photoReference.get()
}
private fun computeImageOrientation(
degrees: Int,
characteristics: Characteristics
) = computeImageOrientation(
screenRotationDegrees = degrees,
cameraRotationDegrees = characteristics.orientation,
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
)
private fun computeDisplayOrientation(
degrees: Int,
characteristics: Characteristics
) = computeDisplayOrientation(
screenRotationDegrees = degrees,
cameraRotationDegrees = characteristics.orientation,
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
)
private fun Camera.updateParameters(newParameters: CameraParameters) {
parameters = parameters.applyNewParameters(newParameters)
}
@Throws(IOException::class)
private fun Camera.setDisplaySurface(
preview: Preview
): Surface = when (preview) {
is Preview.Texture -> preview.surfaceTexture
.also {
setPreviewTexture(it)
}
.let {
Surface(it)
}
is Preview.Surface -> preview.surfaceHolder
.also {
setPreviewDisplay(it)
}
.surface
}
private fun Camera.getPreviewResolution(imageRotation: Int): Resolution {
val previewSize = parameters.previewSize
val size = PreviewSize(
previewSize.width,
previewSize.height
)
return size.run {
when (imageRotation) {
0, 180 -> this
else -> flipDimensions()
}
}
}
private fun PreviewStream.updateProcessorSafely(frameProcessor: ((Frame) -> Unit)?) {
clearProcessors()
when (frameProcessor) {
null -> stop()
else -> {
addProcessor(frameProcessor)
start()
}
}
}
@@ -1,20 +0,0 @@
package io.fotoapparat.hardware;
/**
* A generic camera exception.
*/
public class CameraException extends RuntimeException {
public CameraException(Exception e) {
super(e);
}
public CameraException(String message) {
super(message);
}
public CameraException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,155 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.NonNull;
import java.util.Collections;
import java.util.Set;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.range.Ranges;
/**
* Capabilities of camera hardware.
*/
public class Capabilities {
@NonNull
private final Set<Size> photoSizes;
@NonNull
private final Set<Size> previewSizes;
@NonNull
private final Set<FocusMode> focusModes;
@NonNull
private final Set<Flash> flashModes;
@NonNull
private final Set<Range<Integer>> previewFpsRanges;
@NonNull
private final Range<Integer> sensorSensitivityRange;
private final boolean zoomSupported;
public Capabilities(@NonNull Set<Size> photoSizes,
@NonNull Set<Size> previewSizes,
@NonNull Set<FocusMode> focusModes,
@NonNull Set<Flash> flashModes,
@NonNull Set<Range<Integer>> previewFpsRanges,
@NonNull Range<Integer> sensorSensitivityRange,
boolean zoomSupported) {
this.photoSizes = photoSizes;
this.previewSizes = previewSizes;
this.focusModes = focusModes;
this.flashModes = flashModes;
this.previewFpsRanges = previewFpsRanges;
this.sensorSensitivityRange = sensorSensitivityRange;
this.zoomSupported = zoomSupported;
}
/**
* @return Empty {@link Capabilities}.
*/
public static Capabilities empty() {
return new Capabilities(
Collections.<Size>emptySet(),
Collections.<Size>emptySet(),
Collections.<FocusMode>emptySet(),
Collections.<Flash>emptySet(),
Collections.<Range<Integer>>emptySet(),
Ranges.<Integer>emptyRange(),
false
);
}
/**
* @return list of supported picture sizes.
*/
public Set<Size> supportedPictureSizes() {
return photoSizes;
}
/**
* @return list of supported preview sizes;
*/
public Set<Size> supportedPreviewSizes() {
return previewSizes;
}
/**
* @return list of supported focus modes.
*/
public Set<FocusMode> supportedFocusModes() {
return focusModes;
}
/**
* @return list of supported flash firing modes.
*/
public Set<Flash> supportedFlashModes() {
return flashModes;
}
/**
* @return list of supported preview fps ranges.
*/
public Set<Range<Integer>> supportedPreviewFpsRanges() {
return previewFpsRanges;
}
/**
* @return supported range of the sensor's sensitivity.
*/
public Range<Integer> supportedSensorSensitivityRange() {
return sensorSensitivityRange;
}
/**
* @return {@code true} if zoom feature is supported. {@code false} if it is not supported.
*/
public boolean isZoomSupported() {
return zoomSupported;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Capabilities)) return false;
Capabilities that = (Capabilities) o;
return zoomSupported == that.zoomSupported
&& photoSizes.equals(that.photoSizes)
&& previewSizes.equals(that.previewSizes)
&& focusModes.equals(that.focusModes)
&& flashModes.equals(that.flashModes)
&& previewFpsRanges.equals(that.previewFpsRanges)
&& sensorSensitivityRange.equals(that.sensorSensitivityRange);
}
@Override
public int hashCode() {
int result = photoSizes.hashCode();
result = 31 * result + previewSizes.hashCode();
result = 31 * result + focusModes.hashCode();
result = 31 * result + flashModes.hashCode();
result = 31 * result + previewFpsRanges.hashCode();
result = 31 * result + sensorSensitivityRange.hashCode();
result = 31 * result + (isZoomSupported() ? 1 : 0);
return result;
}
@Override
public String toString() {
return "Capabilities{" +
"photoSizes=" + photoSizes +
", previewSizes=" + previewSizes +
", focusModes=" + focusModes +
", flashModes=" + flashModes +
", previewFpsRanges=" + previewFpsRanges +
", supportedSensorSensitivityRange=" + sensorSensitivityRange +
", zoomSupported=" + zoomSupported +
'}';
}
}
@@ -0,0 +1,193 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.characteristic.getCharacteristics
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.exception.camera.UnsupportedLensException
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.provide.getCameraParameters
import io.fotoapparat.preview.Frame
import io.fotoapparat.view.CameraRenderer
import kotlinx.coroutines.experimental.CompletableDeferred
/**
* Phone.
*/
internal open class Device(
private val logger: Logger,
private val display: Display,
open val scaleType: ScaleType,
open val cameraRenderer: CameraRenderer,
numberOfCameras: Int = Camera.getNumberOfCameras(),
initialConfiguration: CameraConfiguration,
initialLensPositionSelector: Collection<LensPosition>.() -> LensPosition?
) {
private val cameras = (0 until numberOfCameras).map { cameraId ->
CameraDevice(
logger = logger,
characteristics = getCharacteristics(cameraId)
)
}
private var lensPositionSelector: Collection<LensPosition>.() -> LensPosition? = initialLensPositionSelector
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
private var savedConfiguration = CameraConfiguration.default()
init {
updateLensPositionSelector(initialLensPositionSelector)
savedConfiguration = initialConfiguration
}
/**
* Selects a camera.
*/
open fun canSelectCamera(lensPositionSelector: (Collection<LensPosition>) -> LensPosition?): Boolean {
val selectedCameraDevice = selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
return selectedCameraDevice != null
}
/**
* Selects a camera. Will do nothing if camera cannot be selected.
*/
open fun selectCamera() {
logger.recordMethod()
selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
?.let {
selectedCameraDevice.complete(it)
}
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
}
/**
* Clears the selected camera.
*/
open fun clearSelectedCamera() {
selectedCameraDevice = CompletableDeferred()
}
/**
* Waits and returns the selected camera.
*/
open suspend fun awaitSelectedCamera(): CameraDevice {
return selectedCameraDevice.await()
}
/**
* Returns the selected camera.
*
* @throws IllegalStateException If no camera has been yet selected.
* @throws UnsupportedLensException If no camera could get selected.
*/
open fun getSelectedCamera(): CameraDevice {
return try {
selectedCameraDevice.getCompleted()
} catch (e: IllegalStateException) {
throw IllegalStateException("Camera has not started!")
}
}
/**
* @return `true` if a camera has been selected.
*/
open fun hasSelectedCamera() = selectedCameraDevice.isCompleted
/**
* @return rotation of the screen in degrees.
*/
open fun getScreenRotation(): Int {
return display.getRotation()
}
/**
* Updates the desired from the user camera lens position.
*/
open fun updateLensPositionSelector(newLensPosition: Collection<LensPosition>.() -> LensPosition?) {
logger.recordMethod()
lensPositionSelector = newLensPosition
}
/**
* Updates the desired from the user selectors.
*/
open fun updateConfiguration(newConfiguration: Configuration) {
logger.recordMethod()
savedConfiguration = updateConfiguration(
savedConfiguration = savedConfiguration,
newConfiguration = newConfiguration
)
}
/**
* @return The desired from the user selectors.
*/
open fun getConfiguration(): CameraConfiguration {
return savedConfiguration
}
open fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters {
return getCameraParameters(
cameraConfiguration = savedConfiguration,
capabilities = cameraDevice.getCapabilities()
)
}
open fun getFrameProcessor(): (Frame) -> Unit {
return savedConfiguration.frameProcessor
}
/**
* @return The desired from the user camera lens position.
*/
open fun getLensPositionSelector(): Collection<LensPosition>.() -> LensPosition? {
return lensPositionSelector
}
}
/**
* Updates the device's configuration.
*/
internal fun updateConfiguration(
savedConfiguration: CameraConfiguration,
newConfiguration: Configuration
) = CameraConfiguration(
flashMode = newConfiguration.flashMode ?: savedConfiguration.flashMode,
focusMode = newConfiguration.focusMode ?: savedConfiguration.focusMode,
frameProcessor = newConfiguration.frameProcessor ?: savedConfiguration.frameProcessor,
previewFpsRange = newConfiguration.previewFpsRange ?: savedConfiguration.previewFpsRange,
sensorSensitivity = newConfiguration.sensorSensitivity ?: savedConfiguration.sensorSensitivity,
pictureResolution = newConfiguration.pictureResolution ?: savedConfiguration.pictureResolution,
previewResolution = newConfiguration.previewResolution ?: savedConfiguration.previewResolution
)
/**
* Selects a camera from the set of available ones.
*/
internal fun selectCamera(
availableCameras: List<CameraDevice>,
lensPositionSelector: Collection<LensPosition>.() -> LensPosition?
): CameraDevice? {
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
val desiredPosition = lensPositionSelector(lensPositions)
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
}
@@ -0,0 +1,48 @@
package io.fotoapparat.hardware
import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executors
private var taskExecutor = Executors.newSingleThreadExecutor()
get() = field.takeUnless { it.isShutdown } ?: Executors.newSingleThreadExecutor().also { field = it }
private val cameraExecutor = Executors.newSingleThreadExecutor()
private val loggingExecutor = Executors.newSingleThreadExecutor()
private val mainThreadHandler = Handler(Looper.getMainLooper())
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.result.PendingResult].
*/
internal val pendingResultExecutor = Executors.newSingleThreadExecutor()
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.preview.PreviewStream].
*/
internal val frameProcessingExecutor = Executors.newSingleThreadExecutor()
/**
* Shuts down all pending camera tasks.
*/
internal fun shutdownPendingTasks() {
taskExecutor.shutdownNow()
}
/**
* Executes a camera task.
*/
internal fun executeTask(function: Runnable) = taskExecutor.execute(function)
/**
* Executes a camera operation.
*/
internal fun execute(function: () -> Unit) = cameraExecutor.execute(function)
/**
* Executes an operation in the main thread.
*/
internal fun executeLoggingThread(function: () -> Unit) = loggingExecutor.execute { function() }
/**
* Executes an operation in the main thread.
*/
internal fun executeMainThread(function: () -> Unit) = mainThreadHandler.post { function() }
@@ -0,0 +1,27 @@
package io.fotoapparat.hardware.display
import android.content.Context
import android.view.Surface
import android.view.WindowManager
/**
* A phone's display.
*/
open internal class Display(context: Context) {
private val display = context.getDisplay()
/**
* Returns the rotation of the screen from its "natural" orientation in degrees.
*/
open fun getRotation(): Int = when (display.rotation) {
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
Surface.ROTATION_0 -> 0
else -> 0
}
}
private fun Context.getDisplay() = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
@@ -1,16 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.lens.FocusResult;
/**
* Performs auto focus.
*/
public interface AutoFocusOperator {
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
FocusResult autoFocus();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.hardware.Capabilities;
/**
* An interface which indicates that the class can
* provide the camera capabilities.
*/
public interface CapabilitiesOperator {
/**
* Returns the {@link Capabilities} of the opened camera.
*
* @return The {@link Capabilities} of the camera.
*/
Capabilities getCapabilities();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.photo.Photo;
/**
* An interface which indicates that the class can
* capture still pictures.
*/
public interface CaptureOperator {
/**
* Invokes a still picture capture action.
*
* @return The captured photo.
*/
Photo takePicture();
}
@@ -1,23 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.LensPosition;
/**
* An interface which indicates that the class can
* open and close connections to a camera.
*/
public interface ConnectionOperator {
/**
* Opens a connection to a camera.
*
* @param lensPosition The camera position relatively to the screen of the device,
* which will determine which camera to open.
*/
void open(LensPosition lensPosition);
/**
* Closes the connection to a camera.
*/
void close();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* Measures the exposure.
*/
public interface ExposureMeasurementOperator {
/**
* Measures the exposure. This is a blocking operation which returns when measurement completes.
*/
void measureExposure();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* handle the orientation updates.
*/
public interface OrientationOperator {
/**
* Sets the current orientation of the display.
*/
void setDisplayOrientation(int degrees);
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.Parameters;
/**
* An interface which indicates that the class can
* support parameter updates.
*/
public interface ParametersOperator {
/**
* Updates the desired parameters for the preview and the photo capture actions.
*/
void updateParameters(Parameters parameters);
}
@@ -1,21 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* start and stop the capture preview.
*/
public interface PreviewOperator {
/**
* Starts the preview to the surface.
* <p>
* {@link #stopPreview()} should be called
* to stop the operation.
*/
void startPreview();
/**
* Stops the preview from the surface.
*/
void stopPreview();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.preview.PreviewStream;
/**
* Provides {@link PreviewStream} controlled by camera.
*/
public interface PreviewStreamOperator {
/**
* @return {@link PreviewStream} associated with camera.
*/
PreviewStream getPreviewStream();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.RendererParameters;
/**
* Builds {@link RendererParameters}.
*/
public interface RendererParametersOperator {
/**
* @return {@link RendererParameters}.
*/
RendererParameters getRendererParameters();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* set a preview surface.
*/
public interface SurfaceOperator {
/**
* Sets the desired surface on which the camera preview will be displayed.
*/
void setDisplaySurface(Object displaySurface);
}
@@ -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()
}
}
}
@@ -1,39 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
/**
* Provides orientation of the screen.
*/
public class ScreenOrientationProvider {
private final Display display;
public ScreenOrientationProvider(@NonNull Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
}
/**
* @return rotation of the screen in degrees.
*/
public int getScreenRotation() {
int rotation = display.getRotation();
switch (rotation) {
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
case Surface.ROTATION_0:
default:
return 0;
}
}
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.provider;
import java.util.List;
import io.fotoapparat.parameter.LensPosition;
/**
* Provides list of {@link LensPosition} which are available on the device.
*/
public interface AvailableLensPositionsProvider {
/**
* @return list of {@link LensPosition} which are available on the device.
*/
List<LensPosition> getAvailableLensPositions();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.log.Logger;
/**
* Abstraction for providing camera.
*/
public interface CameraProvider {
/**
* @return a {@link CameraDevice}.
*/
CameraDevice get(Logger logger);
}
@@ -1,38 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
/**
* Static factory for {@link CameraProvider}
*/
public class CameraProviders {
private CameraProviders() {
}
/**
* @return provider which uses Camera v2 on devices newer than Lollipop and falls back to Camera
* v1 on older devices.
*/
public static CameraProvider defaultProvider(Context context) {
return new DefaultProvider(
v1(),
v2(context)
);
}
/**
* @return provider for Camera v1.
*/
public static CameraProvider v1() {
return new V1Provider();
}
/**
* @return provider for Camera v2.
*/
public static CameraProvider v2(Context context) {
return new V2Provider(context);
}
}
@@ -1,35 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.log.Logger;
import io.fotoapparat.util.SDKInfo;
/**
* Selects correct version of providers for a device.
*/
public class DefaultProvider implements CameraProvider {
private final CameraProvider v1Provider;
private final CameraProvider v2Provider;
private final SDKInfo sdkInfo;
public DefaultProvider(CameraProvider v1Provider,
CameraProvider v2Provider) {
this(v1Provider, v2Provider, SDKInfo.getInstance());
}
DefaultProvider(CameraProvider v1Provider,
CameraProvider v2Provider,
SDKInfo sdkInfo) {
this.v1Provider = v1Provider;
this.v2Provider = v2Provider;
this.sdkInfo = sdkInfo;
}
@Override
public CameraDevice get(Logger logger) {
return sdkInfo.isBellowLollipop()
? v1Provider.get(logger)
: v2Provider.get(logger);
}
}
@@ -1,37 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.hardware.Camera;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import io.fotoapparat.parameter.LensPosition;
/**
* Provides available {@link LensPosition} using Camera v1 API.
*/
@SuppressWarnings("deprecation")
public class V1AvailableLensPositionProvider implements AvailableLensPositionsProvider {
@Override
public List<LensPosition> getAvailableLensPositions() {
HashSet<LensPosition> positions = new HashSet<>();
final int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(i, cameraInfo);
positions.add(
cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
? LensPosition.FRONT
: LensPosition.BACK
);
}
return new ArrayList<>(positions);
}
}
@@ -1,16 +0,0 @@
package io.fotoapparat.hardware.provider;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.v1.Camera1;
import io.fotoapparat.log.Logger;
/**
* Always provides {@link Camera1}.
*/
public class V1Provider implements CameraProvider {
@Override
public CameraDevice get(Logger logger) {
return new Camera1(logger);
}
}
@@ -1,63 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.fotoapparat.hardware.CameraException;
import io.fotoapparat.parameter.LensPosition;
import static io.fotoapparat.hardware.v2.parameters.converters.LensPositionConverter.toLensPosition;
/**
* Provides available {@link LensPosition} using Camera v2 API.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class V2AvailableLensPositionProvider implements AvailableLensPositionsProvider {
private final CameraManager manager;
public V2AvailableLensPositionProvider(Context context) {
manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}
@Override
public List<LensPosition> getAvailableLensPositions() {
Set<LensPosition> positions = new HashSet<>();
String[] cameraIdList = getCameraIdListUnsafe();
for (String cameraId : cameraIdList) {
Integer lensFacingConstant = getLensPositionUnsafe(cameraId);
positions.add(toLensPosition(lensFacingConstant));
}
return new ArrayList<>(positions);
}
private Integer getLensPositionUnsafe(String cameraId) {
try {
return manager
.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.LENS_FACING);
} catch (CameraAccessException e) {
throw new CameraException(e);
}
}
private String[] getCameraIdListUnsafe() {
try {
return manager.getCameraIdList();
} catch (CameraAccessException e) {
throw new CameraException(e);
}
}
}
@@ -1,146 +0,0 @@
package io.fotoapparat.hardware.provider;
import android.content.Context;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.v2.Camera2;
import io.fotoapparat.hardware.v2.CameraThread;
import io.fotoapparat.hardware.v2.capabilities.CapabilitiesFactory;
import io.fotoapparat.hardware.v2.connection.CameraConnection;
import io.fotoapparat.hardware.v2.lens.executors.CaptureOperatorImpl;
import io.fotoapparat.hardware.v2.lens.executors.ExposureGatheringExecutor;
import io.fotoapparat.hardware.v2.lens.executors.FocusExecutor;
import io.fotoapparat.hardware.v2.lens.operations.LensOperationsFactory;
import io.fotoapparat.hardware.v2.orientation.OrientationManager;
import io.fotoapparat.hardware.v2.parameters.CaptureRequestFactory;
import io.fotoapparat.hardware.v2.parameters.ParametersProvider;
import io.fotoapparat.hardware.v2.parameters.RendererParametersProvider;
import io.fotoapparat.hardware.v2.readers.ContinuousSurfaceReader;
import io.fotoapparat.hardware.v2.readers.StillSurfaceReader;
import io.fotoapparat.hardware.v2.selection.CameraSelector;
import io.fotoapparat.hardware.v2.session.SessionManager;
import io.fotoapparat.hardware.v2.session.SessionProvider;
import io.fotoapparat.hardware.v2.stream.PreviewStream2;
import io.fotoapparat.hardware.v2.surface.TextureManager;
import io.fotoapparat.log.Logger;
/**
* Always provides {@link Camera2}.
*/
public class V2Provider implements CameraProvider {
private static final CameraThread CAMERA_THREAD = new CameraThread();
private final Context context;
public V2Provider(Context context) {
this.context = context;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public CameraDevice get(Logger logger) {
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
AvailableLensPositionsProvider availableLensPositionsProvider = new V2AvailableLensPositionProvider(
context
);
CameraSelector cameraSelector = new CameraSelector(manager);
CameraConnection cameraConnection = new CameraConnection(
cameraSelector,
manager,
CAMERA_THREAD
);
ParametersProvider parametersProvider = new ParametersProvider();
OrientationManager orientationManager = new OrientationManager(
cameraConnection
);
StillSurfaceReader stillSurfaceReader = new StillSurfaceReader(
parametersProvider,
CAMERA_THREAD
);
ContinuousSurfaceReader continuousSurfaceReader = new ContinuousSurfaceReader(
parametersProvider,
CAMERA_THREAD
);
TextureManager textureManager = new TextureManager(
orientationManager,
parametersProvider
);
CaptureRequestFactory captureRequestFactory = new CaptureRequestFactory(
cameraConnection,
stillSurfaceReader,
textureManager,
parametersProvider
);
SessionProvider sessionProvider = new SessionProvider(
stillSurfaceReader,
cameraConnection,
captureRequestFactory,
textureManager,
CAMERA_THREAD
);
SessionManager sessionManager = new SessionManager(
cameraConnection,
sessionProvider
);
CapabilitiesFactory capabilitiesOperator = new CapabilitiesFactory(cameraConnection);
PreviewStream2 previewStream = new PreviewStream2(
continuousSurfaceReader,
parametersProvider,
logger
);
RendererParametersProvider rendererParametersOperator = new RendererParametersProvider(
parametersProvider,
orientationManager
);
LensOperationsFactory lensOperationsFactory = new LensOperationsFactory(
sessionManager,
captureRequestFactory,
CAMERA_THREAD
);
FocusExecutor focusExecutor = new FocusExecutor(
parametersProvider,
lensOperationsFactory
);
ExposureGatheringExecutor exposureGatheringExecutor = new ExposureGatheringExecutor(
lensOperationsFactory
);
CaptureOperatorImpl captureExecutor = new CaptureOperatorImpl(
lensOperationsFactory,
stillSurfaceReader,
orientationManager
);
return new Camera2(
logger,
cameraConnection,
sessionManager,
textureManager,
orientationManager,
parametersProvider,
capabilitiesOperator,
previewStream,
rendererParametersOperator,
focusExecutor,
exposureGatheringExecutor,
captureExecutor,
availableLensPositionsProvider
);
}
}
@@ -1,436 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.hardware.Camera;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.SurfaceView;
import android.view.TextureView;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.CameraException;
import io.fotoapparat.hardware.Capabilities;
import io.fotoapparat.hardware.operators.ParametersOperator;
import io.fotoapparat.hardware.orientation.OrientationUtils;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.hardware.provider.V1AvailableLensPositionProvider;
import io.fotoapparat.hardware.v1.capabilities.CapabilitiesFactory;
import io.fotoapparat.hardware.v1.parameters.SplitParametersOperator;
import io.fotoapparat.hardware.v1.parameters.SupressExceptionsParametersOperator;
import io.fotoapparat.hardware.v1.parameters.SwitchOnFailureParametersOperator;
import io.fotoapparat.hardware.v1.parameters.UnsafeParametersOperator;
import io.fotoapparat.lens.FocusResult;
import io.fotoapparat.log.Logger;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
/**
* Camera hardware driver for v1 {@link Camera} API.
*/
@SuppressWarnings("deprecation")
public class Camera1 implements CameraDevice {
private static final long AUTOFOCUS_TIMEOUT_SECONDS = 3L;
private final CapabilitiesFactory capabilitiesFactory;
private final ParametersConverter parametersConverter;
private final AvailableLensPositionsProvider availableLensPositionsProvider;
private final Logger logger;
private Camera camera;
private int cameraId = -1;
private PreviewStream1 previewStream;
private Throwable lastStacktrace;
private int imageRotation;
@Nullable
private Capabilities cachedCapabilities = null;
@Nullable
private Camera.Parameters cachedZoomParameters = null;
public Camera1(Logger logger) {
this.capabilitiesFactory = new CapabilitiesFactory();
this.parametersConverter = new ParametersConverter();
this.availableLensPositionsProvider = new V1AvailableLensPositionProvider();
this.logger = logger;
}
private static void throwOnFailSetDisplaySurface(Object displaySurface, IOException e) {
throw new CameraException("Unable to set display surface: " + displaySurface, e);
}
@Override
public void open(LensPosition lensPosition) {
recordMethod();
try {
cameraId = cameraIdForLensPosition(lensPosition);
camera = Camera.open(cameraId);
previewStream = new PreviewStream1(camera);
} catch (RuntimeException e) {
throwOnFailedToOpenCamera(lensPosition, e);
}
camera.setErrorCallback(new Camera.ErrorCallback() {
@Override
public void onError(int error, Camera camera) {
if (lastStacktrace != null) {
lastStacktrace.printStackTrace();
}
logger.log("Camera error code: " + error);
}
});
}
private void throwOnFailedToOpenCamera(LensPosition lensPosition, RuntimeException e) {
throw new CameraException(
"Failed to open camera with lens position: " + lensPosition + " and id: " + cameraId,
e
);
}
private int cameraIdForLensPosition(LensPosition lensPosition) {
int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = getCameraInfo(i);
if (info.facing == facingForLensPosition(lensPosition)) {
return i;
}
}
return 0;
}
private int facingForLensPosition(LensPosition lensPosition) {
switch (lensPosition) {
case FRONT:
return Camera.CameraInfo.CAMERA_FACING_FRONT;
case BACK:
return Camera.CameraInfo.CAMERA_FACING_BACK;
default:
throw new IllegalArgumentException("Camera is not supported: " + lensPosition);
}
}
@Override
public void close() {
recordMethod();
cachedCapabilities = null;
if (isCameraOpened()) {
camera.release();
}
}
@Override
public void startPreview() {
recordMethod();
try {
camera.startPreview();
} catch (RuntimeException e) {
throwOnFailStartPreview(e);
}
}
private void throwOnFailStartPreview(RuntimeException e) {
throw new CameraException(
"Failed to start preview for camera devices: " + cameraId,
e
);
}
@Override
public void stopPreview() {
recordMethod();
if (isCameraOpened()) {
camera.stopPreview();
}
}
@Override
public void setDisplaySurface(Object displaySurface) {
recordMethod();
try {
trySetDisplaySurface(displaySurface);
} catch (IOException e) {
throwOnFailSetDisplaySurface(displaySurface, e);
}
}
@Override
public void setDisplayOrientation(int degrees) {
recordMethod();
if (!isCameraOpened()) {
return;
}
Camera.CameraInfo info = getCameraInfo(cameraId);
imageRotation = computeImageOrientation(degrees, info);
camera.setDisplayOrientation(
computeDisplayOrientation(degrees, info)
);
previewStream.setFrameOrientation(imageRotation);
}
private int computeDisplayOrientation(int screenRotationDegrees,
Camera.CameraInfo info) {
return OrientationUtils.computeDisplayOrientation(
screenRotationDegrees,
info.orientation,
info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
);
}
private int computeImageOrientation(int screenRotationDegrees,
Camera.CameraInfo info) {
return OrientationUtils.computeImageOrientation(
screenRotationDegrees,
info.orientation,
info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
);
}
@Override
public void updateParameters(Parameters parameters) {
recordMethod();
parametersOperator().updateParameters(parameters);
cachedZoomParameters = null;
}
@Override
public Parameters getCurrentParameters() {
Camera.Parameters platformParameters = camera.getParameters();
return parametersConverter.fromPlatformParameters(
new CameraParametersDecorator(platformParameters)
);
}
@NonNull
private SwitchOnFailureParametersOperator parametersOperator() {
ParametersOperator unsafeParametersOperator = new UnsafeParametersOperator(
camera,
parametersConverter
);
ParametersOperator fallbackOperator = new SplitParametersOperator(
new SupressExceptionsParametersOperator(
unsafeParametersOperator,
logger
)
);
return new SwitchOnFailureParametersOperator(
unsafeParametersOperator,
fallbackOperator
);
}
@Override
public Capabilities getCapabilities() {
if (cachedCapabilities != null) {
return cachedCapabilities;
}
recordMethod();
Capabilities capabilities = capabilitiesFactory.fromParameters(
new CameraParametersDecorator(camera.getParameters())
);
cachedCapabilities = capabilities;
return capabilities;
}
private void trySetDisplaySurface(Object displaySurface) throws IOException {
if (displaySurface instanceof TextureView) {
camera.setPreviewTexture(((TextureView) displaySurface).getSurfaceTexture());
} else if (displaySurface instanceof SurfaceView) {
camera.setPreviewDisplay(((SurfaceView) displaySurface).getHolder());
} else {
throw new IllegalArgumentException("Unsupported display surface: " + displaySurface);
}
}
@Override
public Photo takePicture() {
recordMethod();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Photo> photoReference = new AtomicReference<>();
camera.takePicture(
null,
null,
null,
new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
photoReference.set(
new Photo(data, imageRotation)
);
latch.countDown();
}
}
);
try {
latch.await();
} catch (InterruptedException e) {
// Do nothing
}
return photoReference.get();
}
@Override
public PreviewStream getPreviewStream() {
recordMethod();
return isPreviewStreamInitialized()
? previewStream
: PreviewStream.NULL;
}
private boolean isPreviewStreamInitialized() {
return previewStream != null;
}
@Override
public RendererParameters getRendererParameters() {
recordMethod();
RendererParameters rendererParameters = new RendererParameters(
previewSize(),
imageRotation
);
logRendererParameters(rendererParameters);
return rendererParameters;
}
private void logRendererParameters(RendererParameters rendererParameters) {
logger.log("Renderer parameters are: " + rendererParameters);
}
@Override
public FocusResult autoFocus() {
recordMethod();
final CountDownLatch latch = new CountDownLatch(1);
try {
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
latch.countDown();
}
});
} catch (Exception e) {
logFailedAutoFocus(e);
return FocusResult.none();
}
try {
latch.await(AUTOFOCUS_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// Do nothing
}
return FocusResult.successNoMeasurement();
}
private void logFailedAutoFocus(Exception e) {
logger.log("Failed to perform autofocus using device " + cameraId + " e: " + e.getMessage());
}
@Override
public void measureExposure() {
// Do nothing. Not supported by Camera1.
}
@Override
public List<LensPosition> getAvailableLensPositions() {
return availableLensPositionsProvider.getAvailableLensPositions();
}
@Override
public void setZoom(@FloatRange(from = 0f, to = 1f) float level) {
try {
setZoomUnsafe(level);
} catch (Exception e) {
logFailedZoomUpdate(level, e);
}
}
private void setZoomUnsafe(@FloatRange(from = 0f, to = 1f) float level) {
if (cachedZoomParameters == null) {
cachedZoomParameters = camera.getParameters();
}
cachedZoomParameters.setZoom(
(int) (cachedZoomParameters.getMaxZoom() * level)
);
camera.setParameters(cachedZoomParameters);
}
private void logFailedZoomUpdate(float level, Exception e) {
logger.log("Unable to change zoom level to " + level + " e: " + e.getMessage());
}
private Size previewSize() {
Camera.Size previewSize = camera.getParameters().getPreviewSize();
return new Size(
previewSize.width,
previewSize.height
);
}
@NonNull
private Camera.CameraInfo getCameraInfo(int id) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
return info;
}
private boolean isCameraOpened() {
return camera != null;
}
private void recordMethod() {
lastStacktrace = new Exception();
logger.log(
lastStacktrace.getStackTrace()[1].getMethodName()
);
}
}
@@ -1,228 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Decorator for {@link Camera.Parameters} with methods
* for getting and settings additional camera parameters.
*/
@SuppressWarnings("deprecation")
public class CameraParametersDecorator {
/* Raw camera params keys */
private static final String[] RAW_ISO_SUPPORTED_VALUES_KEYS = {
"iso-values", "iso-mode-values", "iso-speed-values", "nv-picture-iso-values"
};
private static final String[] RAW_ISO_CURRENT_VALUE_KEYS = {
"iso", "iso-speed", "nv-picture-iso"
};
private Camera.Parameters cameraParameters;
public CameraParametersDecorator(Camera.Parameters cameraParameters) {
this.cameraParameters = cameraParameters;
}
/**
* @return {@link Camera.Parameters} view of this decorator.
*/
public Camera.Parameters asCameraParameters() {
return cameraParameters;
}
/**
* @see Camera.Parameters#isZoomSupported()
*/
public boolean isZoomSupported() {
return cameraParameters.isZoomSupported();
}
/**
* @see Camera.Parameters#getSupportedPreviewSizes()
*/
public List<Size> getSupportedPreviewSizes() {
return cameraParameters.getSupportedPreviewSizes();
}
/**
* @see Camera.Parameters#getSupportedPictureSizes()
*/
public List<Size> getSupportedPictureSizes() {
return cameraParameters.getSupportedPictureSizes();
}
/**
* @see Camera.Parameters#getSupportedFlashModes()
*/
public List<String> getSupportedFlashModes() {
return cameraParameters.getSupportedFlashModes();
}
/**
* @see Camera.Parameters#getSupportedFocusModes()
*/
public List<String> getSupportedFocusModes() {
return cameraParameters.getSupportedFocusModes();
}
/**
* @see Camera.Parameters#getSupportedPreviewFpsRange()
*/
public List<int[]> getSupportedPreviewFpsRange() {
return cameraParameters.getSupportedPreviewFpsRange();
}
/**
* @see Camera.Parameters#getFocusMode()
*/
public String getFocusMode() {
return cameraParameters.getFocusMode();
}
/**
* @see Camera.Parameters#setFocusMode(String)
*/
public void setFocusMode(String focusMode) {
cameraParameters.setFocusMode(focusMode);
}
/**
* @see Camera.Parameters#getFlashMode()
*/
public String getFlashMode() {
return cameraParameters.getFlashMode();
}
/**
* @see Camera.Parameters#setFlashMode(String)
*/
public void setFlashMode(String flash) {
cameraParameters.setFlashMode(flash);
}
/**
* @see Camera.Parameters#getPictureSize()
*/
public Camera.Size getPictureSize() {
return cameraParameters.getPictureSize();
}
/**
* @see Camera.Parameters#getPreviewSize()
*/
public Camera.Size getPreviewSize() {
return cameraParameters.getPreviewSize();
}
/**
* @see Camera.Parameters#setPreviewSize(int, int)
*/
public void setPreviewSize(int width, int height) {
cameraParameters.setPreviewSize(width, height);
}
/**
* @see Camera.Parameters#setPictureSize(int, int)
*/
public void setPictureSize(int width, int height) {
cameraParameters.setPictureSize(width, height);
}
/**
* @see Camera.Parameters#setPreviewFpsRange(int, int)
*/
public void setPreviewFpsRange(int min, int max) {
cameraParameters.setPreviewFpsRange(min, max);
}
/**
* Returns set of ISO values, that camera supports.
*
* @return the set of supported ISO values.
*/
@NonNull
public Set<Integer> getSensorSensitivityValues() {
String[] rawValues = extractRawCameraValues(RAW_ISO_SUPPORTED_VALUES_KEYS);
return convertParamsToInts(rawValues);
}
/**
* Sets ISO value for camera.
*/
public void setSensorSensitivityValue(int isoValue) {
String isoKey = findExistingKey(RAW_ISO_CURRENT_VALUE_KEYS);
if (isoKey != null) {
cameraParameters.set(isoKey, isoValue);
}
}
/**
* @see Camera.Parameters#getJpegQuality()
*/
public int getJpegQuality(){
return cameraParameters.getJpegQuality();
}
/**
* @see Camera.Parameters#setJpegQuality(int)
*/
public void setJpegQuality(int quality){
cameraParameters.setJpegQuality(quality);
}
@Nullable
private String findExistingKey(@NonNull String[] keys) {
for (String key : keys) {
if (cameraParameters.get(key) != null) {
return key;
}
}
return null;
}
@Nullable
private String[] extractRawCameraValues(@NonNull String[] keys) {
for (String key : keys) {
String[] rawValues = extractStringValuesFromParams(key);
if (rawValues != null) {
return rawValues;
}
}
return null;
}
@Nullable
private String[] extractStringValuesFromParams(@NonNull String key) {
String rawValues = cameraParameters.get(key);
if (rawValues != null) {
return rawValues.split(",");
} else {
return null;
}
}
@NonNull
private Set<Integer> convertParamsToInts(@Nullable String[] values) {
Set<Integer> integerValues = new HashSet<>();
if (values != null) {
for (String value : values) {
try {
integerValues.add(Integer.valueOf(value.trim()));
} catch (NumberFormatException e) {
// Found not number option. Skip it.
}
}
}
return integerValues;
}
}
@@ -1,161 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.hardware.Camera;
import io.fotoapparat.hardware.v1.capabilities.FlashCapability;
import io.fotoapparat.hardware.v1.capabilities.FocusCapability;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
/**
* Converts {@link Parameters} to {@link CameraParametersDecorator}.
*/
@SuppressWarnings("deprecation")
public class ParametersConverter {
/**
* Converts {@link Parameters} to {@link CameraParametersDecorator}.
*
* @param parameters parameters which should be converted.
* @param output output value. It is required because of C-style API in Camera v1.
* @return same object which was passed as {@code output}, but filled with new parameters.
*/
public CameraParametersDecorator toPlatformParameters(Parameters parameters,
CameraParametersDecorator output) {
for (Parameters.Type storedType : parameters.storedTypes()) {
applyParameter(
storedType,
parameters,
output
);
}
return output;
}
/**
* Converts {@link Camera.Parameters} to {@link Parameters}.
*
* @param platformParameters parameters which should be converted.
* @return Converted parameters object
*/
public Parameters fromPlatformParameters(CameraParametersDecorator platformParameters) {
Parameters parameters = new Parameters();
FocusMode focusMode = FocusCapability.toFocusMode(platformParameters.getFocusMode());
parameters.putValue(Parameters.Type.FOCUS_MODE, focusMode);
Flash flash = FlashCapability.toFlash(platformParameters.getFlashMode());
parameters.putValue(Parameters.Type.FLASH, flash);
Camera.Size platformSize = platformParameters.getPictureSize();
Size pictureSize = new Size(platformSize.width, platformSize.height);
parameters.putValue(Parameters.Type.PICTURE_SIZE, pictureSize);
Camera.Size platformPreviewSize = platformParameters.getPreviewSize();
Size previewSize = new Size(platformPreviewSize.width, platformPreviewSize.height);
parameters.putValue(Parameters.Type.PREVIEW_SIZE, previewSize);
Integer jpegQuality = platformParameters.getJpegQuality();
parameters.putValue(Parameters.Type.JPEG_QUALITY, jpegQuality);
return parameters;
}
private void applyParameter(Parameters.Type type,
Parameters input,
CameraParametersDecorator output) {
switch (type) {
case FOCUS_MODE:
applyFocusMode(
(FocusMode) input.getValue(type),
output
);
break;
case FLASH:
applyFlash(
(Flash) input.getValue(type),
output
);
break;
case PICTURE_SIZE:
applyPictureSize(
(Size) input.getValue(type),
output
);
break;
case PREVIEW_SIZE:
applyPreviewSize(
(Size) input.getValue(type),
output
);
break;
case PREVIEW_FPS_RANGE:
applyPreviewFpsRange(
getRange(type, input),
output
);
break;
case SENSOR_SENSITIVITY:
applySensorSensitivity(
(Integer) input.getValue(type),
output
);
break;
case JPEG_QUALITY:
applyJpegQuality(
(Integer) input.getValue(type),
output);
}
}
@SuppressWarnings("unchecked")
private Range<Integer> getRange(Parameters.Type type, Parameters input) {
return (Range<Integer>) input.getValue(type);
}
private void applyPreviewSize(Size size,
CameraParametersDecorator output) {
output.setPreviewSize(size.width, size.height);
}
private void applyPictureSize(Size size,
CameraParametersDecorator output) {
output.setPictureSize(size.width, size.height);
}
private void applyFlash(Flash flash,
CameraParametersDecorator output) {
output.setFlashMode(
FlashCapability.toCode(flash)
);
}
private void applyFocusMode(FocusMode focusMode,
CameraParametersDecorator output) {
output.setFocusMode(
FocusCapability.toCode(focusMode)
);
}
private void applyPreviewFpsRange(Range<Integer> fpsRange,
CameraParametersDecorator output) {
output.setPreviewFpsRange(
fpsRange.lowest(), fpsRange.highest()
);
}
private void applySensorSensitivity(Integer value,
CameraParametersDecorator output) {
output.setSensorSensitivityValue(value);
}
private void applyJpegQuality(Integer value,
CameraParametersDecorator output){
output.setJpegQuality(value);
}
}
@@ -1,133 +0,0 @@
package io.fotoapparat.hardware.v1;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.preview.Frame;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.preview.PreviewStream;
/**
* {@link PreviewStream} of Camera v1.
*/
@SuppressWarnings("deprecation")
public class PreviewStream1 implements PreviewStream {
private static Executor FRAME_PROCESSORS_EXECUTOR = Executors.newSingleThreadExecutor();
private final Camera camera;
private final Set<FrameProcessor> frameProcessors = new LinkedHashSet<>();
private Size previewSize = null;
private int frameOrientation = 0;
public PreviewStream1(Camera camera) {
this.camera = camera;
}
/**
* @param frameOrientation CW rotation of frames in degrees.
*/
public void setFrameOrientation(int frameOrientation) {
this.frameOrientation = frameOrientation;
}
@Override
public void addFrameToBuffer() {
camera.addCallbackBuffer(
allocateBuffer(camera.getParameters())
);
}
private byte[] allocateBuffer(Camera.Parameters parameters) {
ensureNv21Format(parameters);
Camera.Size previewSize = parameters.getPreviewSize();
this.previewSize = new Size(
previewSize.width,
previewSize.height
);
return new byte[bytesPerFrame(previewSize)];
}
private int bytesPerFrame(Camera.Size previewSize) {
return (previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(ImageFormat.NV21)) / 8;
}
private void ensureNv21Format(Camera.Parameters parameters) {
if (parameters.getPreviewFormat() != ImageFormat.NV21) {
throw new UnsupportedOperationException("Only NV21 preview format is supported");
}
}
@Override
public void addProcessor(@NonNull FrameProcessor processor) {
synchronized (frameProcessors) {
frameProcessors.add(processor);
}
}
@Override
public void removeProcessor(@NonNull FrameProcessor processor) {
synchronized (frameProcessors) {
frameProcessors.remove(processor);
}
}
@Override
public void start() {
addFrameToBuffer();
camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
dispatchFrameOnBackgroundThread(data);
}
});
}
private void dispatchFrameOnBackgroundThread(final byte[] data) {
FRAME_PROCESSORS_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
synchronized (frameProcessors) {
dispatchFrame(data);
}
}
});
}
private void dispatchFrame(byte[] image) {
ensurePreviewSizeAvailable();
final Frame frame = new Frame(previewSize, image, frameOrientation);
for (final FrameProcessor frameProcessor : frameProcessors) {
frameProcessor.processFrame(frame);
}
returnFrameToBuffer(frame);
}
private void ensurePreviewSizeAvailable() {
if (previewSize == null) {
throw new IllegalStateException("previewSize is null. Frame was not added?");
}
}
private void returnFrameToBuffer(Frame frame) {
camera.addCallbackBuffer(
frame.image
);
}
}
@@ -1,123 +0,0 @@
package io.fotoapparat.hardware.v1.capabilities;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.fotoapparat.hardware.Capabilities;
import io.fotoapparat.hardware.v1.Camera1;
import io.fotoapparat.hardware.v1.CameraParametersDecorator;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.range.Ranges;
/**
* {@link Capabilities} of {@link Camera1}.
*/
@SuppressWarnings("deprecation")
public class CapabilitiesFactory {
/**
* @return {@link Capabilities} from given camera parameters.
*/
public Capabilities fromParameters(CameraParametersDecorator parametersProvider) {
return new Capabilities(
extractPictureSizes(parametersProvider),
extractPreviewSizes(parametersProvider),
extractFocusModes(parametersProvider),
extractFlashModes(parametersProvider),
extractPreviewFpsRanges(parametersProvider),
extractSensorSensitivityRange(parametersProvider),
parametersProvider.isZoomSupported()
);
}
private Set<Size> extractPreviewSizes(CameraParametersDecorator parametersProvider) {
return mapSizes(
parametersProvider.getSupportedPreviewSizes()
);
}
private Set<Size> extractPictureSizes(CameraParametersDecorator parametersProvider) {
return mapSizes(
parametersProvider.getSupportedPictureSizes()
);
}
private Set<Size> mapSizes(Collection<Camera.Size> sizes) {
HashSet<Size> result = new HashSet<>();
for (Camera.Size size : sizes) {
result.add(new Size(
size.width,
size.height
));
}
return result;
}
private Set<Flash> extractFlashModes(CameraParametersDecorator parameters) {
HashSet<Flash> result = new HashSet<>();
for (String flashMode : supportedFlashModes(parameters)) {
result.add(
FlashCapability.toFlash(flashMode)
);
}
return result;
}
@NonNull
private List<String> supportedFlashModes(CameraParametersDecorator parametersProvider) {
List<String> supportedFlashModes = parametersProvider.getSupportedFlashModes();
return supportedFlashModes != null
? supportedFlashModes
: Collections.singletonList(Camera.Parameters.FLASH_MODE_OFF);
}
private Set<FocusMode> extractFocusModes(CameraParametersDecorator parametersProvider) {
HashSet<FocusMode> result = new HashSet<>();
for (String focusMode : parametersProvider.getSupportedFocusModes()) {
result.add(
FocusCapability.toFocusMode(focusMode)
);
}
result.add(FocusMode.FIXED);
return result;
}
private Set<Range<Integer>> extractPreviewFpsRanges(CameraParametersDecorator parametersProvider) {
List<int[]> fpsRanges = parametersProvider.getSupportedPreviewFpsRange();
if (fpsRanges == null) {
return Collections.emptySet();
}
Set<Range<Integer>> wrappedFpsRanges = new HashSet<>(fpsRanges.size());
for (int[] range : fpsRanges) {
wrappedFpsRanges.add(Ranges.continuousRange(
range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]
));
}
return wrappedFpsRanges;
}
@NonNull
private Range<Integer> extractSensorSensitivityRange(CameraParametersDecorator parametersProvider) {
final Set<Integer> isoValuesSet = parametersProvider.getSensorSensitivityValues();
return Ranges.discreteRange(isoValuesSet);
}
}

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