google/jetpack-camera-app

IllegalStateException after backgrounding and coming back to the app

yasith opened this issue · 2 comments

yasith commented

Steps to reproduce:

  • Start the app
  • Double tap on screen to flip camera
  • Background the app (observe camera green light at top-right corner is on - meaning camera is running)
  • Foreground the app

Expected:

  • User backgrounds the app
  • Camera stops running
  • User comes back to the app
  • Camera starts running

Observed:

  • User backgrounds the app
  • Camera continues running
  • User comes back to the app
  • Camera tries to run, and we get a crash
FATAL EXCEPTION: main
Process: com.google.jetpackcamera, PID: 10962
java.lang.IllegalStateException: Use case Preview:androidx.camera.core.Preview-f44934f5-30e4-4fd7-a14c-f8ad26e06060 already bound to a different lifecycle.
	at androidx.camera.lifecycle.ProcessCameraProvider.bindToLifecycle(ProcessCameraProvider.java:605)
	at androidx.camera.lifecycle.ProcessCameraProvider.bindToLifecycle(ProcessCameraProvider.java:415)
	at com.google.jetpackcamera.domain.camera.CoroutineCameraProviderKt$runWith$2.invokeSuspend(CoroutineCameraProvider.kt:43)
	at com.google.jetpackcamera.domain.camera.CoroutineCameraProviderKt$runWith$2.invoke(Unknown Source:8)
	at com.google.jetpackcamera.domain.camera.CoroutineCameraProviderKt$runWith$2.invoke(Unknown Source:4)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
	at com.google.jetpackcamera.domain.camera.CoroutineCameraProviderKt.runWith(CoroutineCameraProvider.kt:41)
	at com.google.jetpackcamera.domain.camera.CameraXCameraUseCase$runCamera$2.invokeSuspend(CameraXCameraUseCase.kt:123)
	at com.google.jetpackcamera.domain.camera.CameraXCameraUseCase$runCamera$2.invoke(Unknown Source:8)
	at com.google.jetpackcamera.domain.camera.CameraXCameraUseCase$runCamera$2.invoke(Unknown Source:4)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
	at com.google.jetpackcamera.domain.camera.CameraXCameraUseCase.runCamera(CameraXCameraUseCase.kt:115)
	at com.google.jetpackcamera.feature.preview.PreviewViewModel$runCamera$1.invokeSuspend(PreviewViewModel.kt:88)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
	at com.google.jetpackcamera.feature.preview.PreviewViewModel.runCamera(PreviewViewModel.kt:86)
	at com.google.jetpackcamera.feature.preview.PreviewScreenKt$PreviewScreen$1$1.invokeSuspend(PreviewScreen.kt:113)
	at com.google.jetpackcamera.feature.preview.PreviewScreenKt$PreviewScreen$1$1.invoke(Unknown Source:8)
	at com.google.jetpackcamera.feature.preview.PreviewScreenKt$PreviewScreen$1$1.invoke(Unknown Source:4)
	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:111)
	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:8)
	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:4)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:110)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
	at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)

yasith commented

I observed that when the app is backgrounded viewModel.stopCamera() gets called. However camera is not shut down, it's possible that runningCameraJob is null, or already cancelled.

We might be switching the active Job when we rebind the camera. The ViewModel should be updated with the active Job.

I'm this occurring again on main with 1 additional step of changing the aspect ratio.

Repro steps:

  1. Change aspect ratio to 1:1 in quick settings
  2. Background app by going to home screen
  3. Return to JCA and observe crash

We should create a test using UiAutomator to background the app and return to the app. We can also add another test to do the same but set the aspect ratio to 1:1 before backgrounding to catch this new issue.