Skip to content

Commit

Permalink
feat: implement internal camera functionality with capture and previe…
Browse files Browse the repository at this point in the history
…w use cases [FoKE-Developers#45]
  • Loading branch information
ing03201 committed Jan 12, 2025
1 parent 8bea38c commit 7fd306a
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 33 deletions.
1 change: 1 addition & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.camerax.view)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.foke.together.domain.interactor

import android.content.Context
import com.foke.together.domain.output.ImageRepositoryInterface
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import com.foke.together.util.AppLog
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class CaptureWithInternalCameraUseCase @Inject constructor(
private val internalCameraRepository: InternalCameraRepositoryInterface,
private val imageRepository: ImageRepositoryInterface
) {
suspend operator fun invoke(
context: Context,
fileName: String
): Result<Unit>{
internalCameraRepository.capture(context)
.onSuccess {
AppLog.i(TAG, "capture", "success: $it")
imageRepository.cachingImage(it, fileName)
return Result.success(Unit)
}
.onFailure {
AppLog.i(TAG, "capture", "failure: $it")
return Result.failure(it)
}
return Result.failure(Exception("Unknown error"))
}

companion object {
private val TAG = CaptureWithInternalCameraUseCase::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.foke.together.domain.interactor

import androidx.camera.view.PreviewView
import androidx.lifecycle.LifecycleOwner
import com.foke.together.domain.output.ExternalCameraRepositoryInterface
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import javax.inject.Inject

class GetInternalCameraPreviewUseCase @Inject constructor(
private val internalCameraRepository: InternalCameraRepositoryInterface
) {
suspend operator fun invoke(
previewView: PreviewView,
lifecycleOwner: LifecycleOwner
) = internalCameraRepository.showCameraPreview(
previewView,
lifecycleOwner
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.foke.together.domain.output

import android.content.Context
import android.graphics.Bitmap
import androidx.camera.view.PreviewView
import androidx.lifecycle.LifecycleOwner

interface InternalCameraRepositoryInterface {
suspend fun capture(context: Context): Result<Bitmap>
suspend fun showCameraPreview(
previewView: PreviewView,
lifecycleOwner: LifecycleOwner
)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package com.foke.together.external.camera.internal

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import android.hardware.camera2.CaptureRequest
import android.os.SystemClock
import android.util.Log
import android.util.Range
import android.util.Size
import androidx.annotation.OptIn
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.interop.Camera2Interop
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.AspectRatio
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -30,19 +26,19 @@ import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object CameraModule {
private val TAG = CameraModule::class.java.simpleName
private var cameraExecutorService : ExecutorService? = null

object InternalCameraModule {
private val TAG = InternalCameraModule::class.java.simpleName
private var analyzeExecutorService : ExecutorService? = null
private var captureExecutorService : ExecutorService? = null

// CameraX ImageAnalyzer용 백그라운드 서비스
@Provides
@Singleton
fun clearCameraExecutor(): Boolean{
cameraExecutorService?.shutdown()
cameraExecutorService?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
cameraExecutorService = null
return cameraExecutorService == null
fun clearAnalyzeExecutor(): Boolean{
analyzeExecutorService?.shutdown()
analyzeExecutorService?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
analyzeExecutorService = null
return analyzeExecutorService == null
}

@Provides
Expand All @@ -65,30 +61,30 @@ object CameraModule {
@Provides
@Singleton
fun provideImageAnalysis(
rotation: Int
context: Context
): ImageAnalysis {
val iaBuilder = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.setTargetRotation(rotation)
.setTargetRotation(context.display.rotation)
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
return iaBuilder.build()
}

@Provides
@Singleton
fun provideCameraExecutor(): ExecutorService {
if(cameraExecutorService == null){
cameraExecutorService = Executors.newSingleThreadExecutor()
fun provideAnalyzeExecutor(): ExecutorService {
if(analyzeExecutorService == null){
analyzeExecutorService = Executors.newSingleThreadExecutor()
}
return cameraExecutorService!!
return analyzeExecutorService!!
}

fun shutdown(){
if(cameraExecutorService != null) {
cameraExecutorService?.shutdown()
cameraExecutorService?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
cameraExecutorService = null
if(analyzeExecutorService != null) {
analyzeExecutorService?.shutdown()
analyzeExecutorService?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
analyzeExecutorService = null
}
}

Expand All @@ -104,9 +100,29 @@ object CameraModule {
@Provides
@Singleton
fun provideCameraStatus(): Flow<Boolean> = flow {
emit(cameraExecutorService != null)
emit(analyzeExecutorService != null)
}

@Provides
@Singleton
fun provideCameraPreview(
previewView: PreviewView
): Preview {
return Preview.Builder().build().also{
it.surfaceProvider = previewView.surfaceProvider
}
}

@Provides
@Singleton
fun provideImageCapture(
context: Context
): ImageCapture {
return ImageCapture.Builder()
.setTargetRotation(context.display.rotation)
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.build()
}
// 미디어 파이프 제공하기 위함
// @Provides
// @Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.foke.together.external.repository

import android.content.Context
import android.graphics.Bitmap
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import com.foke.together.util.AppLog
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class InternalCameraRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val cameraProvider: ProcessCameraProvider,
private val selector : CameraSelector,
private val preview: Preview,
private val imageAnalysis: ImageAnalysis,
private val imageCapture: ImageCapture

): InternalCameraRepositoryInterface{

override suspend fun capture(context: Context): Result<Bitmap> {
var imageBitmap : Bitmap? = null
imageCapture.takePicture(
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(imageProxy: ImageProxy) {
imageBitmap = imageProxy.toBitmap()
}

override fun onError(exception: ImageCaptureException) {
imageBitmap = null
}
}
)
return if(imageBitmap != null){
Result.success(imageBitmap!!)
} else{
Result.failure(Exception("Unknown error"))
}
}

override suspend fun showCameraPreview(
previewView: PreviewView,
lifecycleOwner: LifecycleOwner
) {
try{
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
selector,
preview,
imageAnalysis,
imageCapture
)
}
catch (e: Exception){
AppLog.e(TAG,"showCameraPreview", e.message!!)
}
}

companion object {
private val TAG = InternalCameraRepository::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package com.foke.together.external.repository.di

import com.foke.together.domain.output.ExternalCameraRepositoryInterface
import com.foke.together.domain.output.ImageRepositoryInterface
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import com.foke.together.domain.output.QRCodeRepositoryInterface
import com.foke.together.domain.output.SessionRepositoryInterface
import com.foke.together.external.repository.ExternalCameraRepository
import com.foke.together.external.repository.ImageRepository
import com.foke.together.external.repository.InternalCameraRepository
import com.foke.together.external.repository.QRCodeRepository
import com.foke.together.external.repository.SessionRepository
import dagger.Binds
Expand All @@ -19,10 +21,16 @@ import javax.inject.Singleton
abstract class RepositoryModule {
@Singleton
@Binds
abstract fun bindAppPreferenceRepository(
abstract fun bindExternalCameraRepository(
externalCameraRepository: ExternalCameraRepository
): ExternalCameraRepositoryInterface

@Singleton
@Binds
abstract fun bindInternalCameraRepository(
internalCameraRepository: InternalCameraRepository
): InternalCameraRepositoryInterface

@Singleton
@Binds
abstract fun bindImageRepository(
Expand Down

0 comments on commit 7fd306a

Please sign in to comment.