Merge "Camera2: Stop repeating request for abandoned output" into nyc-dev
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index b542339..5743b4d 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -921,7 +921,16 @@
                 int requestId = mRepeatingRequestId;
                 mRepeatingRequestId = REQUEST_ID_NONE;
 
-                long lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
+                long lastFrameNumber;
+                try {
+                    lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
+                } catch (IllegalArgumentException e) {
+                    if (DEBUG) {
+                        Log.v(TAG, "Repeating request was already stopped for request " + requestId);
+                    }
+                    // Repeating request was already stopped. Nothing more to do.
+                    return;
+                }
 
                 checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
             }
@@ -1686,6 +1695,24 @@
         }
 
         @Override
+        public void onRepeatingRequestError(long lastFrameNumber) {
+            if (DEBUG) {
+                Log.d(TAG, "Repeating request error received. Last frame number is " +
+                        lastFrameNumber);
+            }
+
+            synchronized(mInterfaceLock) {
+                // Camera is already closed or no repeating request is present.
+                if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) {
+                    return; // Camera already closed
+                }
+
+                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
+                mRepeatingRequestId = REQUEST_ID_NONE;
+            }
+        }
+
+        @Override
         public void onDeviceIdle() {
             if (DEBUG) {
                 Log.d(TAG, "Camera now idle");
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index b0b94e3..e48bce1 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -76,6 +76,7 @@
         void onBusy();
         void onCaptureStarted(RequestHolder holder, long timestamp);
         void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
+        void onRepeatingRequestError(long lastFrameNumber);
     }
 
     /**
@@ -201,6 +202,22 @@
     }
 
     /**
+     * Set repeating request error.
+     *
+     * <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p>
+     *
+     * @param lastFrameNumber Frame number of the last repeating request before it is stopped.
+     */
+    public synchronized void setRepeatingRequestError(final long lastFrameNumber) {
+        mCurrentHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mCurrentListener.onRepeatingRequestError(lastFrameNumber);
+            }
+        });
+    }
+
+    /**
      * Set the listener for state transition callbacks.
      *
      * @param handler handler on which to call the callbacks.
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index f99928a..acbf214 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -205,6 +205,7 @@
         private static final int CAPTURE_STARTED = 2;
         private static final int RESULT_RECEIVED = 3;
         private static final int PREPARED = 4;
+        private static final int REPEATING_REQUEST_ERROR = 5;
 
         private final HandlerThread mHandlerThread;
         private Handler mHandler;
@@ -261,6 +262,15 @@
             getHandler().sendMessage(msg);
         }
 
+
+        @Override
+        public void onRepeatingRequestError(long lastFrameNumber) {
+            Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
+                    /*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL),
+                    /*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL));
+            getHandler().sendMessage(msg);
+        }
+
         @Override
         public IBinder asBinder() {
             // This is solely intended to be used for in-process binding.
@@ -311,6 +321,12 @@
                             mCallbacks.onPrepared(streamId);
                             break;
                         }
+                        case REPEATING_REQUEST_ERROR: {
+                            long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL;
+                            lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL);
+                            mCallbacks.onRepeatingRequestError(lastFrameNumber);
+                            break;
+                        }
                         default:
                             throw new IllegalArgumentException(
                                 "Unknown callback message " + msg.what);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 6c95869..3e79118 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -242,6 +242,25 @@
                 }
             });
         }
+
+        @Override
+        public void onRepeatingRequestError(final long lastFrameNumber) {
+            mResultHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onRepeatingRequestError callback.");
+                    }
+                    try {
+                        mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber);
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Received remote exception during onRepeatingRequestError " +
+                                "callback: ", e);
+                    }
+                }
+            });
+        }
     };
 
     private final RequestThreadManager mRequestThreadManager;
@@ -397,8 +416,15 @@
                     "submitRequestList - Empty/null requests are not allowed");
         }
 
-        List<Long> surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
-                getSurfaceIds(mConfiguredSurfaces);
+        List<Long> surfaceIds;
+
+        try {
+            surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
+                    getSurfaceIds(mConfiguredSurfaces);
+        } catch (BufferQueueAbandonedException e) {
+            throw new ServiceSpecificException(BAD_VALUE,
+                    "submitRequestList - configured surface is abandoned.");
+        }
 
         // Make sure that there all requests have at least 1 surface; all surfaces are non-null
         for (CaptureRequest request : requestList) {
@@ -674,12 +700,17 @@
         LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
     }
 
-    static long getSurfaceId(Surface surface) {
+    static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
         checkNotNull(surface);
-        return nativeGetSurfaceId(surface);
+        try {
+            return nativeGetSurfaceId(surface);
+        } catch (IllegalArgumentException e) {
+            throw new BufferQueueAbandonedException();
+        }
     }
 
-    static List<Long> getSurfaceIds(SparseArray<Surface> surfaces) {
+    static List<Long> getSurfaceIds(SparseArray<Surface> surfaces)
+            throws BufferQueueAbandonedException {
         if (surfaces == null) {
             throw new NullPointerException("Null argument surfaces");
         }
@@ -696,7 +727,8 @@
         return surfaceIds;
     }
 
-    static List<Long> getSurfaceIds(Collection<Surface> surfaces) {
+    static List<Long> getSurfaceIds(Collection<Surface> surfaces)
+            throws BufferQueueAbandonedException {
         if (surfaces == null) {
             throw new NullPointerException("Null argument surfaces");
         }
@@ -713,7 +745,13 @@
     }
 
     static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
-        long id = getSurfaceId(s);
+        long id = 0;
+        try {
+            id = getSurfaceId(s);
+        } catch (BufferQueueAbandonedException e) {
+            // If surface is abandoned, return false.
+            return false;
+        }
         return ids.contains(id);
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 476c3de..98b761b 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -40,6 +40,7 @@
     private final int mNumJpegTargets;
     private final int mNumPreviewTargets;
     private volatile boolean mFailed = false;
+    private boolean mOutputAbandoned = false;
 
     private final Collection<Long> mJpegSurfaceIds;
 
@@ -266,4 +267,17 @@
         return mFailed;
     }
 
+    /**
+     * Mark at least one of this request's output surfaces is abandoned.
+     */
+    public void setOutputAbandoned() {
+        mOutputAbandoned = true;
+    }
+
+    /**
+     * Return if any of this request's output surface is abandoned.
+     */
+    public boolean isOutputAbandoned() {
+        return mOutputAbandoned;
+    }
 }
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index a3fdd56..da62f54 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -710,6 +710,7 @@
                     break;
                 case MSG_SUBMIT_CAPTURE_REQUEST:
                     Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
+                    boolean anyRequestOutputAbandoned = false;
 
                     // Get the next burst from the request queue.
                     Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
@@ -910,7 +911,22 @@
                         if (!holder.requestFailed()) {
                             mDeviceState.setCaptureResult(holder, result);
                         }
+
+                        if (holder.isOutputAbandoned()) {
+                            anyRequestOutputAbandoned = true;
+                        }
                     }
+
+                    // Stop the repeating request if any of its output surfaces is abandoned.
+                    if (anyRequestOutputAbandoned && nextBurst.first.isRepeating()) {
+                        long lastFrameNumber = cancelRepeating(nextBurst.first.getRequestId());
+                        if (DEBUG) {
+                            Log.d(TAG, "Stopped repeating request. Last frame number is " +
+                                    lastFrameNumber);
+                        }
+                        mDeviceState.setRepeatingRequestError(lastFrameNumber);
+                    }
+
                     if (DEBUG) {
                         long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
                         Log.d(TAG, "Capture request took " + totalTime + " ns");
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 70bc2fd..e0d3905 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -525,9 +525,16 @@
         checkEglError("makeCurrent");
     }
 
-    private boolean swapBuffers(EGLSurface surface) {
+    private boolean swapBuffers(EGLSurface surface)
+            throws LegacyExceptionUtils.BufferQueueAbandonedException {
         boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
-        checkEglError("swapBuffers");
+        int error = EGL14.eglGetError();
+        if (error == EGL14.EGL_BAD_SURFACE) {
+            throw new LegacyExceptionUtils.BufferQueueAbandonedException();
+        } else if (error != EGL14.EGL_SUCCESS) {
+            throw new IllegalStateException("swapBuffers: EGL error: 0x" +
+                    Integer.toHexString(error));
+        }
         return result;
     }
 
@@ -722,7 +729,14 @@
             addGlTimestamp(timestamp);
         }
 
-        List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
+        List<Long> targetSurfaceIds = new ArrayList();
+        try {
+            targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
+        } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+            Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+            request.setOutputAbandoned();
+        }
+
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
                 try{
@@ -737,6 +751,7 @@
                     swapBuffers(holder.eglSurface);
                 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                     Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+                    request.setOutputAbandoned();
                 }
             }
         }
@@ -761,6 +776,7 @@
                             holder.width, holder.height, format);
                 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                     Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+                    request.setOutputAbandoned();
                 }
             }
         }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 9a0946e..bbc249f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -254,6 +254,15 @@
             // TODO Auto-generated method stub
 
         }
+
+        /*
+         * (non-Javadoc)
+         * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
+         */
+        @Override
+        public void onRepeatingRequestError(long lastFrameNumber) {
+            // TODO Auto-generated method stub
+        }
     }
 
     @SmallTest
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 5c1d8a7..6c879b9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -145,6 +145,15 @@
             // TODO Auto-generated method stub
 
         }
+
+        /*
+         * (non-Javadoc)
+         * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
+         */
+        @Override
+        public void onRepeatingRequestError(long lastFrameNumber) {
+            // TODO Auto-generated method stub
+        }
     }
 
     class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {