camera2: Make legacy error handling more robust.

- Relax internal timeout for JPEG captures in LEGACY mode.
- Make RequestThreadManager.quit() idempotent to avoid queuing
  messages on a dead thread's handler.
- Catch RuntimeExceptions from other Camera1 API methods to
  allow proper cleanup + release of Camera1 API client.

Bug: 19255187
Change-Id: I6cb08bb6b832b0d0df6ee6e8983c35de2df4a408
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 6535a4e..f1f2f0c 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -44,6 +44,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static com.android.internal.util.Preconditions.*;
 
@@ -68,7 +69,7 @@
     // For slightly more spammy messages that will get repeated every frame
     private static final boolean VERBOSE =
             Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.VERBOSE);
-    private final Camera mCamera;
+    private Camera mCamera;
     private final CameraCharacteristics mCharacteristics;
 
     private final CameraDeviceState mDeviceState;
@@ -83,8 +84,8 @@
     private static final int MAX_IN_FLIGHT_REQUESTS = 2;
 
     private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
-    private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
-    private static final int REQUEST_COMPLETE_TIMEOUT = 3000; // ms (same as JPEG timeout)
+    private static final int JPEG_FRAME_TIMEOUT = 4000; // ms (same as CTS for API2)
+    private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT; // ms (same as JPEG timeout)
 
     private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
     private boolean mPreviewRunning = false;
@@ -108,6 +109,8 @@
     private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
     private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
 
+    private final AtomicBoolean mQuit = new AtomicBoolean(false);
+
     // Stuff JPEGs into HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers to get around SW write
     // limitations for (b/17379185).
     private static final boolean USE_BLOB_FORMAT_OVERRIDE = true;
@@ -325,7 +328,15 @@
             Log.d(TAG, "configureOutputs with " + outputsStr);
         }
 
-        stopPreview();
+        try {
+            stopPreview();
+        }  catch (RuntimeException e) {
+            Log.e(TAG, "Received device exception in configure call: ", e);
+            mDeviceState.setError(
+                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+            return;
+        }
+
         /*
          * Try to release the previous preview's surface texture earlier if we end up
          * using a different one; this also reduces the likelihood of getting into a deadlock
@@ -335,6 +346,11 @@
             mCamera.setPreviewTexture(/*surfaceTexture*/null);
         } catch (IOException e) {
             Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Received device exception in configure call: ", e);
+            mDeviceState.setError(
+                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+            return;
         }
 
         if (mGLThreadManager != null) {
@@ -470,7 +486,14 @@
             mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
         }
 
-        mCamera.setParameters(mParams);
+        try {
+            mCamera.setParameters(mParams);
+        } catch (RuntimeException e) {
+                Log.e(TAG, "Received device exception while configuring: ", e);
+                mDeviceState.setError(
+                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+
+        }
     }
 
     private void resetJpegSurfaceFormats(Collection<Surface> surfaces) {
@@ -793,7 +816,7 @@
                             }
 
                         } catch (IOException e) {
-                            Log.e(TAG, "Received device exception: ", e);
+                            Log.e(TAG, "Received device exception during capture call: ", e);
                             mDeviceState.setError(
                                     CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
                             break;
@@ -802,6 +825,11 @@
                             mDeviceState.setError(
                                     CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
                             break;
+                        } catch (RuntimeException e) {
+                            Log.e(TAG, "Received device exception during capture call: ", e);
+                            mDeviceState.setError(
+                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                            break;
                         }
 
                         if (paramsChanged) {
@@ -878,9 +906,11 @@
                     }
                     if (mGLThreadManager != null) {
                         mGLThreadManager.quit();
+                        mGLThreadManager = null;
                     }
                     if (mCamera != null) {
                         mCamera.release();
+                        mCamera = null;
                     }
                     resetJpegSurfaceFormats(mCallbackOutputs);
                     break;
@@ -942,14 +972,16 @@
      * Quit the request thread, and clean up everything.
      */
     public void quit() {
-        Handler handler = mRequestThread.waitAndGetHandler();
-        handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
-        mRequestThread.quitSafely();
-        try {
-            mRequestThread.join();
-        } catch (InterruptedException e) {
-            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
-                    mRequestThread.getName(), mRequestThread.getId()));
+        if (!mQuit.getAndSet(true)) {  // Avoid sending messages on dead thread's handler.
+            Handler handler = mRequestThread.waitAndGetHandler();
+            handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
+            mRequestThread.quitSafely();
+            try {
+                mRequestThread.join();
+            } catch (InterruptedException e) {
+                Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+                        mRequestThread.getName(), mRequestThread.getId()));
+            }
         }
     }