diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index e54e570..c3e45c2 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -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) diff --git a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithInternalCameraUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithInternalCameraUseCase.kt new file mode 100644 index 0000000..5be4d2b --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithInternalCameraUseCase.kt @@ -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{ + 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 + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/GetInternalCameraPreviewUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/GetInternalCameraPreviewUseCase.kt new file mode 100644 index 0000000..02e2114 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/GetInternalCameraPreviewUseCase.kt @@ -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 + ) +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt new file mode 100644 index 0000000..88b81b7 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt @@ -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 + suspend fun showCameraPreview( + previewView: PreviewView, + lifecycleOwner: LifecycleOwner + ) +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/camera/internal/AndroidCameraDataSource.kt b/external/src/main/java/com/foke/together/external/camera/internal/AndroidCameraDataSource.kt deleted file mode 100644 index 55f38dd..0000000 --- a/external/src/main/java/com/foke/together/external/camera/internal/AndroidCameraDataSource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.foke.together.external.camera.internal - -class AndroidCameraDataSource { - // TODO: add android camera api here -} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/camera/internal/InternalCameraModule.kt b/external/src/main/java/com/foke/together/external/camera/internal/InternalCameraModule.kt index c898d57..711222a 100644 --- a/external/src/main/java/com/foke/together/external/camera/internal/InternalCameraModule.kt +++ b/external/src/main/java/com/foke/together/external/camera/internal/InternalCameraModule.kt @@ -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 @@ -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 @@ -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 } } @@ -104,9 +100,29 @@ object CameraModule { @Provides @Singleton fun provideCameraStatus(): Flow = 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 diff --git a/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt b/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt new file mode 100644 index 0000000..2c5862d --- /dev/null +++ b/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt @@ -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 { + 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 + } +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt b/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt index c408c65..96b2ee1 100644 --- a/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt +++ b/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt @@ -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 @@ -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(