diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f5bee99..f711c5c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,6 +38,11 @@
+
+
+
+
+
if (granted) {
- startCamera()
+ // パーミッションが付与されたら onResume でカメラが開始される
} else if (!succeedToShowDialog) {
PermissionDialog.show(this, CAMERA_PERMISSION_REQUEST_KEY)
} else {
@@ -63,12 +67,16 @@ class MainActivity : AppCompatActivity() {
Settings.get()
}
private var resultSet: Set = emptySet()
+ private var isMushroomMode: Boolean = false // Mushroom mode 用フラグ
override fun onCreate(
savedInstanceState: Bundle?,
) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
+ // Mushroom mode の判定 (intent.action を確認)
+ isMushroomMode = intent.action?.contains(ACTION_INTERCEPT_MAIN) ?: false
+
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
@@ -87,11 +95,11 @@ class MainActivity : AppCompatActivity() {
DividerItemDecoration(this, DividerItemDecoration.VERTICAL),
)
vibrator = getSystemService()!!
- codeScanner = CodeScanner(this, binding.previewView, ::onDetectCode)
+ codeScanner = CodeScanner(this, binding.previewView, ::onDetectCode) // CodeScanner はここで初期化
binding.flash.setOnClickListener {
codeScanner.toggleTorch()
}
- codeScanner.getTouchStateStream().observe(this) {
+ codeScanner.getTouchStateStream().observe(this) { // メソッド名を getTouchStateStream に戻す
onFlashOn(it)
}
detectedPresenter = DetectedPresenter(
@@ -116,29 +124,54 @@ class MainActivity : AppCompatActivity() {
expandList()
}
}
- if (CameraPermission.hasPermission(this)) {
- startCamera()
- Updater.startIfAvailable(this)
- } else {
- launcher.launch()
- }
PermissionDialog.registerListener(this, CAMERA_PERMISSION_REQUEST_KEY) {
finishByError()
}
OptionsMenuPresenter(this, binding.menu).setUp()
+ Updater.startIfAvailable(this) // Updaterはパーミッション状態に関わらず呼べるならここに
}
- override fun onRestart() {
- super.onRestart()
- if (!started) {
- if (CameraPermission.hasPermission(this)) {
- startCamera()
- } else {
- finishByError()
+ // onStart を追加
+ override fun onStart() {
+ super.onStart()
+ if (!CameraPermission.hasPermission(this)) {
+ launcher.launch()
+ }
+ // パーミッションがある場合は onResume でカメラが開始される
+ }
+
+ // onResume を追加/修正
+ override fun onResume() {
+ super.onResume()
+ if (CameraPermission.hasPermission(this)) {
+ if (::codeScanner.isInitialized) { // codeScannerが初期化済みか確認
+ codeScanner.start() // カメラの初期化/再初期化
+ codeScanner.resume() // 解析の再開
}
}
}
+ // onPause を追加
+ override fun onPause() {
+ super.onPause()
+ if (::codeScanner.isInitialized) { // codeScannerが初期化済みか確認
+ codeScanner.pause() // 解析の一時停止
+ codeScanner.shutdownCamera() // カメラリソースの解放
+ }
+ }
+
+ override fun onRestart() {
+ super.onRestart()
+ // 以前の onRestart のロジックは onStart と onResume でカバーされるため、
+ // ここでの特別なカメラ処理は不要になる。
+ }
+
+ // onDestroy を追加 (任意、デバッグや最終確認用)
+ override fun onDestroy() {
+ // onPause で shutdownCamera が呼ばれるため、通常は不要。
+ super.onDestroy()
+ }
+
private fun finishByError() {
toastPermissionError()
super.finish()
@@ -166,18 +199,12 @@ class MainActivity : AppCompatActivity() {
binding.flash.setImageResource(icon)
}
- private fun startCamera() {
- if (started) return
- started = true
- codeScanner.start()
- }
-
private fun onDetectCode(
imageProxy: ImageProxy,
codes: List,
) {
val detected = mutableListOf()
- codes.forEach {
+ codes.forEach { // it を使用
val value = it.rawValue ?: return@forEach
val result = ScanResult(
value = value,
@@ -186,12 +213,23 @@ class MainActivity : AppCompatActivity() {
isUrl = it.valueType == Barcode.TYPE_URL,
)
if (!resultSet.contains(result)) {
+ if (isMushroomMode) { // Mushroom mode の処理を先に行う
+ val data = Intent()
+ data.putExtra(REPLACE_KEY_MAIN, result.value)
+ setResult(RESULT_OK, data)
+ finish()
+ return // Mushroomモードでは最初の検出でActivityを終了
+ }
viewModel.add(result)
vibrate()
detected.add(it)
}
}
- if (detected.isEmpty()) return
+ if (detected.isEmpty()) {
+ // imageProxy.close() は CodeAnalyzer 内で自動的に行われるはずなので、
+ // ここでの呼び出しは不要。
+ return
+ }
detectedPresenter.onDetected(imageProxy, detected)
}
diff --git a/app/src/main/kotlin/net/mm2d/codereader/code/CodeScanner.kt b/app/src/main/kotlin/net/mm2d/codereader/code/CodeScanner.kt
index e7255a4..f57676a 100644
--- a/app/src/main/kotlin/net/mm2d/codereader/code/CodeScanner.kt
+++ b/app/src/main/kotlin/net/mm2d/codereader/code/CodeScanner.kt
@@ -14,6 +14,7 @@ import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.TorchState
+import androidx.camera.core.UseCase
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.lifecycle.ProcessCameraProvider
@@ -40,13 +41,20 @@ class CodeScanner(
private val analyzer: CodeAnalyzer = CodeAnalyzer(scanner, callback)
private var camera: Camera? = null
private val torchStateFlow: MutableStateFlow = MutableStateFlow(false)
+
+ private var cameraProviderInternal: ProcessCameraProvider? = null
+ private var previewUseCaseInternal: Preview? = null
+ private var analysisUseCaseInternal: ImageAnalysis? = null
+
fun getTouchStateStream(): Flow = torchStateFlow
init {
activity.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event == Event.ON_DESTROY) {
- workerExecutor.shutdown()
+ if (!workerExecutor.isShutdown) {
+ workerExecutor.shutdown()
+ }
scanner.close()
}
},
@@ -56,7 +64,13 @@ class CodeScanner(
fun start() {
val future = ProcessCameraProvider.getInstance(activity)
future.addListener({
- setUp(future.get())
+ try {
+ val provider = future.get()
+ this.cameraProviderInternal = provider
+ setUp(provider)
+ } catch (e: Exception) {
+ Timber.e(e, "CodeScanner: Failed to get ProcessCameraProvider in start() listener.")
+ }
}, ContextCompat.getMainExecutor(activity))
}
@@ -66,36 +80,59 @@ class CodeScanner(
val resolutionSelector = ResolutionSelector.Builder()
.setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY)
.build()
+
val preview = Preview.Builder()
.setResolutionSelector(resolutionSelector)
.build()
preview.surfaceProvider = previewView.surfaceProvider
+ this.previewUseCaseInternal = preview
val analysis = ImageAnalysis.Builder()
.setResolutionSelector(resolutionSelector)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
analysis.setAnalyzer(workerExecutor, analyzer)
+ this.analysisUseCaseInternal = analysis
try {
provider.unbindAll()
val camera = provider.bindToLifecycle(
activity,
CameraSelector.DEFAULT_BACK_CAMERA,
- preview,
- analysis,
+ previewUseCaseInternal!!,
+ analysisUseCaseInternal!!,
)
+ this.camera = camera
camera.cameraInfo.torchState.observe(activity) { state ->
torchStateFlow.tryEmit(state == TorchState.ON)
}
- this.camera = camera
} catch (e: Exception) {
- Timber.e(e)
+ Timber.e(e, "CodeScanner: Use case binding failed in setUp.")
+ this.camera = null
+ }
+ }
+
+ fun shutdownCamera() {
+ cameraProviderInternal?.let { provider ->
+ val useCasesToUnbind = mutableListOf()
+ previewUseCaseInternal?.let { useCasesToUnbind.add(it) }
+ analysisUseCaseInternal?.let { useCasesToUnbind.add(it) }
+
+ if (useCasesToUnbind.isNotEmpty()) {
+ try {
+ provider.unbind(*useCasesToUnbind.toTypedArray())
+ } catch (e: Exception) {
+ Timber.e(e, "CodeScanner: [shutdownCamera] Error unbinding specific use cases: ${e.message}")
+ }
+ }
}
+ previewUseCaseInternal = null
+ analysisUseCaseInternal = null
+ camera = null
}
fun toggleTorch() {
- val camera = camera ?: return
+ val camera = this.camera ?: return
camera.cameraControl.enableTorch(!torchStateFlow.value)
}