I am using Mobile Vision API to detect faces on front facing camera. I used Google's FaceTracker demo, but I need to use it in Fragment. It is working, but when I try to rotate screen, application crashes with exception RuntimeException: Fail to connect to camera service
.
Fragment code:
class EyeTrackerFragment : Fragment(), AnkoLogger {
private var cameraSource: CameraSource? = null
private lateinit var cameraSourcePreview: CameraSourcePreview
private lateinit var graphicOverlay: GraphicOverlay
private var callback: OnEyeContextUpdatedListener? = null
companion object {
// google play services error code
private val RC_HANDLE_GMS = 9001
// permission request codes need to be < 256
private val RC_HANDLE_CAMERA_PERM = 2
}
override fun onAttach(context: Context?) {
super.onAttach(context)
try {
callback = activity as OnEyeContextUpdatedListener
} catch (e: ClassCastException) {
throw ClassCastException("${activity} must implement OnEyeContextUpdatedListener")
}
}
/**
* Restarts the camera.
*/
override fun onResume() {
super.onResume()
startCameraSource()
}
/**
* Stops the camera.
*/
override fun onPause() {
super.onPause()
cameraSourcePreview.stop()
}
/**
* Releases the resources associated with the camera source, the associated detector, and the
* rest of the processing pipeline.
*/
override fun onDestroy() {
super.onDestroy()
cameraSource?.release()
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater!!.inflate(R.layout.fragment_face_tracker, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
cameraSourcePreview = getView()!!.findViewById<View>(R.id.preview) as CameraSourcePreview
graphicOverlay = getView()!!.findViewById<View>(R.id.faceOverlay) as GraphicOverlay
if (ActivityCompat.checkSelfPermission(context.applicationContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
createCameraSource()
} else {
requestCameraPermission()
}
}
/**
* Handles the requesting of the camera permission. This includes
* showing a "Toast" message of why the permission is needed then
* sending the request.
*/
private fun requestCameraPermission() {
warn("Camera permission is not granted. Requesting permission")
val permissions = arrayOf(Manifest.permission.CAMERA)
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(activity, permissions, RC_HANDLE_CAMERA_PERM)
return
}
Toast.makeText(context.applicationContext, "Give me permissions", Toast.LENGTH_LONG).show()
}
private fun createCameraSource() {
val detector = FaceDetector.Builder(context.applicationContext)
.setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
.setProminentFaceOnly(true)
.setTrackingEnabled(true)
.setMode(FaceDetector.ACCURATE_MODE)
.build()
val faceTracker = GraphicFaceTracker(graphicOverlay, callback, context)
val faceProcessor = LargestFaceFocusingProcessor(detector, faceTracker)
detector.setProcessor(faceProcessor)
if (!detector.isOperational) {
longToast("Face detector dependencies are not yet available.")
}
cameraSource = CameraSource.Builder(context, detector)
.setRequestedPreviewSize(640, 480)
.setFacing(CameraSource.CAMERA_FACING_FRONT)
.setRequestedFps(30.0f)
.build()
}
private fun startCameraSource() {
// check that the device has play services available.
val code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
context.applicationContext)
if (code != ConnectionResult.SUCCESS) {
val dlg = GoogleApiAvailability.getInstance().getErrorDialog(activity, code, RC_HANDLE_GMS)
dlg.show()
}
if (cameraSource != null) {
try {
cameraSourcePreview.start(cameraSource!!, graphicOverlay)
} catch (e: IOException) {
error("Unable to start camera source.", e)
cameraSource!!.release()
cameraSource = null
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode != RC_HANDLE_CAMERA_PERM) {
debug("Got unexpected permission result: $requestCode")
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
return
}
if (grantResults.size != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
debug("Camera permission granted - initialize the camera source")
createCameraSource()
return
}
error("Permission not granted: results len = ${grantResults.size}. Result code = ${if (grantResults.isNotEmpty()) grantResults[0] else "(empty)"}")
val builder = AlertDialog.Builder(context)
builder.setTitle("Face Tracker sample")
.setMessage("Have no camera permission")
.setPositiveButton("Ok", { _, _ -> activity.finish() })
.show()
}
}
According to Activity code. It only attaches previous fragment in onCreate method and then does not do anything.
I guess that camera instance was not released in some situation, but it is done in the same way like in Activity. Is there any special behavior in Fragment that would require extra actions in comparision with activies?
Full exception log:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.eyetracker.android.demo, PID: 22631
java.lang.RuntimeException: Fail to connect to camera service
at android.hardware.Camera.<init>(Camera.java:520)
at android.hardware.Camera.open(Camera.java:361)
at com.google.android.gms.vision.CameraSource.zzchq(Unknown Source)
at com.google.android.gms.vision.CameraSource.start(Unknown Source)
at com.eyetracker.android.camera.CameraSourcePreview.startIfReady(CameraSourcePreview.kt:82)
at com.eyetracker.android.camera.CameraSourcePreview.access$startIfReady(CameraSourcePreview.kt:18)
at com.eyetracker.android.camera.CameraSourcePreview$SurfaceCallback.surfaceCreated(CameraSourcePreview.kt:104)
at android.view.SurfaceView.updateWindow(SurfaceView.java:580)
at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:176)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:948)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1974)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1065)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5901)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:550)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:211)
at android.app.ActivityThread.main(ActivityThread.java:5389)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
Whenever Android destroys and recreates your Activity for orientation change, it calls onSaveInstanceState().
So do save necessary data in a Bundle in onSaveInstanceState() and restore the same in onCreate() by checking if field savedInstanceState is null?