Merge "Camera: Propagate extension results in case of dropped frames" into tm-dev
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1263da6..4d0ba63 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -72,7 +72,7 @@
 import java.util.concurrent.Executor;
 
 public final class CameraExtensionSessionImpl extends CameraExtensionSession {
-    private static final int PREVIEW_QUEUE_SIZE = 3;
+    private static final int PREVIEW_QUEUE_SIZE = 10;
     private static final String TAG = "CameraExtensionSessionImpl";
 
     private final Executor mExecutor;
@@ -1057,15 +1057,8 @@
                                                 mClientRequest));
 
                         if (mCaptureResultHandler != null) {
-                            CameraMetadataNative captureResults = new CameraMetadataNative();
-                            for (CaptureResult.Key key : mSupportedResultKeys) {
-                                Object value = result.get(key);
-                                if (value != null) {
-                                    captureResults.set(key, value);
-                                }
-                            }
                             mCaptureResultHandler.onCaptureCompleted(timestamp,
-                                    captureResults);
+                                    initializeFilteredResults(result));
                         }
                     } finally {
                         Binder.restoreCallingIdentity(ident);
@@ -1127,6 +1120,11 @@
 
         private class ImageCallback implements OnImageAvailableListener {
             @Override
+            public void onImageDropped(long timestamp) {
+                notifyCaptureFailed();
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (mCaptureFailed) {
                     img.close();
@@ -1160,6 +1158,9 @@
 
     private class ImageLoopbackCallback implements OnImageAvailableListener {
         @Override
+        public void onImageDropped(long timestamp) { }
+
+        @Override
         public void onImageAvailable(ImageReader reader, Image img) {
             img.close();
         }
@@ -1221,7 +1222,8 @@
     }
 
     private interface OnImageAvailableListener {
-        public void onImageAvailable (ImageReader reader, Image img);
+        void onImageDropped(long timestamp);
+        void onImageAvailable (ImageReader reader, Image img);
     }
 
     private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener,
@@ -1263,6 +1265,29 @@
             } else {
                 mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null));
             }
+
+            notifyDroppedImages(timestamp);
+        }
+
+        private void notifyDroppedImages(long timestamp) {
+            Set<Long> timestamps = mImageListenerMap.keySet();
+            ArrayList<Long> removedTs = new ArrayList<>();
+            for (long ts : timestamps) {
+                if (ts < timestamp) {
+                    Log.e(TAG, "Dropped image with ts: " + ts);
+                    Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
+                    if (entry.second != null) {
+                        entry.second.onImageDropped(ts);
+                    }
+                    if (entry.first != null) {
+                        entry.first.close();
+                    }
+                    removedTs.add(ts);
+                }
+            }
+            for (long ts : removedTs) {
+                mImageListenerMap.remove(ts);
+            }
         }
 
         public void registerListener(Long timestamp, OnImageAvailableListener listener) {
@@ -1291,6 +1316,12 @@
                     entry.first.close();
                 }
             }
+            for (long timestamp : mImageListenerMap.keySet()) {
+                Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp);
+                if (entry.second != null) {
+                    entry.second.onImageDropped(timestamp);
+                }
+            }
             mImageListenerMap.clear();
         }
     }
@@ -1447,7 +1478,6 @@
             } else {
                 notifyConfigurationFailure();
             }
-
         }
 
         @Override
@@ -1536,7 +1566,16 @@
                     } else if (mPreviewProcessorType ==
                             IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
                         int idx = mPendingResultMap.indexOfKey(timestamp);
-                        if (idx >= 0) {
+
+                        if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
+                            // Image was dropped before we can receive the capture results
+                            if ((mCaptureResultHandler != null)) {
+                                mCaptureResultHandler.onCaptureCompleted(timestamp,
+                                        initializeFilteredResults(result));
+                            }
+                            discardPendingRepeatingResults(idx, mPendingResultMap, false);
+                        } else  if (idx >= 0) {
+                            // Image came before the capture results
                             ParcelImage parcelImage = initializeParcelImage(
                                     mPendingResultMap.get(timestamp).first);
                             try {
@@ -1563,6 +1602,7 @@
                             }
                             discardPendingRepeatingResults(idx, mPendingResultMap, false);
                         } else {
+                            // Image not yet available
                             notifyClient = false;
                             mPendingResultMap.put(timestamp,
                                     new Pair<>(null,
@@ -1581,16 +1621,8 @@
                                                 mClientRequest));
                                 if ((mCaptureResultHandler != null) && (mPreviewProcessorType !=
                                         IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) {
-                                    CameraMetadataNative captureResults =
-                                            new CameraMetadataNative();
-                                    for (CaptureResult.Key key : mSupportedResultKeys) {
-                                        Object value = result.get(key);
-                                        if (value != null) {
-                                            captureResults.set(key, value);
-                                        }
-                                    }
                                     mCaptureResultHandler.onCaptureCompleted(timestamp,
-                                            captureResults);
+                                            initializeFilteredResults(result));
                                 }
                             } else {
                                 mExecutor.execute(
@@ -1657,19 +1689,24 @@
             for (int i = idx; i >= 0; i--) {
                 if (previewMap.valueAt(i).first != null) {
                     previewMap.valueAt(i).first.close();
-                } else {
-                    if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
-                        Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
-                        final long ident = Binder.clearCallingIdentity();
-                        try {
-                            mExecutor.execute(
-                                    () -> mCallbacks
-                                            .onCaptureFailed(CameraExtensionSessionImpl.this,
-                                                    mClientRequest));
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
+                } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) &&
+                        ((i != idx) || notifyCurrentIndex)) {
+                    TotalCaptureResult result = previewMap.valueAt(i).second;
+                    Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+                    mCaptureResultHandler.onCaptureCompleted(timestamp,
+                            initializeFilteredResults(result));
+
+                    Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(
+                                () -> mCallbacks
+                                        .onCaptureFailed(CameraExtensionSessionImpl.this,
+                                                mClientRequest));
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
+
                 }
                 previewMap.removeAt(i);
             }
@@ -1683,6 +1720,12 @@
             }
 
             @Override
+            public void onImageDropped(long timestamp) {
+                discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+                        mPendingResultMap, true);
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (img == null) {
                     Log.e(TAG, "Invalid image!");
@@ -1703,6 +1746,15 @@
         private class ImageProcessCallback implements OnImageAvailableListener {
 
             @Override
+            public void onImageDropped(long timestamp) {
+                discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+                        mPendingResultMap, true);
+                // Add an empty frame&results entry to flag that we dropped a frame
+                // and valid capture results can immediately return to client.
+                mPendingResultMap.put(timestamp, new Pair<>(null, null));
+            }
+
+            @Override
             public void onImageAvailable(ImageReader reader, Image img) {
                 if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) {
                     // We reached the maximum acquired images limit. This is possible in case we
@@ -1768,6 +1820,17 @@
         }
     }
 
+    private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) {
+        CameraMetadataNative captureResults = new CameraMetadataNative();
+        for (CaptureResult.Key key : mSupportedResultKeys) {
+            Object value = result.get(key);
+            if (value != null) {
+                captureResults.set(key, value);
+            }
+        }
+        return captureResults;
+    }
+
     private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
             @NonNull Size arSize) {
         final float TOLL = .01f;