Merge "camera2: validate reprocess request's session"
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 31e6e25..0cf8df1 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -65,6 +65,12 @@
 public abstract class CameraCaptureSession implements AutoCloseable {
 
     /**
+     * Used to identify invalid session ID.
+     * @hide
+     */
+    public static final int SESSION_ID_NONE = -1;
+
+    /**
      * Get the camera device that this session is created for.
      */
     public abstract CameraDevice getDevice();
@@ -168,10 +174,11 @@
      * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not
      *                                  configured as outputs for this session; or a reprocess
      *                                  capture request is submitted in a non-reprocessible capture
-     *                                  session; or the capture targets a Surface in the middle
-     *                                  of being {@link #prepare prepared}; or the handler is
-     *                                  null, the listener is not null, and the calling thread has
-     *                                  no looper.
+     *                                  session; or the reprocess capture request was created with
+     *                                  a {@link TotalCaptureResult} from a different session; or
+     *                                  the capture targets a Surface in the middle of being
+     *                                  {@link #prepare prepared}; or the handler is null, the
+     *                                  listener is not null, and the calling thread has no looper.
      *
      * @see #captureBurst
      * @see #setRepeatingRequest
@@ -226,7 +233,9 @@
      *                                  capture request is submitted in a non-reprocessible capture
      *                                  session; or the list of requests contains both requests to
      *                                  capture images from the camera and reprocess capture
-     *                                  requests; or one of the captures targets a Surface in the
+     *                                  requests; or one of the reprocess capture requests was
+     *                                  created with a {@link TotalCaptureResult} from a different
+     *                                  session; or one of the captures targets a Surface in the
      *                                  middle of being {@link #prepare prepared}; or if the handler
      *                                  is null, the listener is not null, and the calling thread
      *                                  has no looper.
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 35727e8..19d17b1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -158,6 +158,9 @@
     private final HashSet<Surface> mSurfaceSet;
     private final CameraMetadataNative mSettings;
     private boolean mIsReprocess;
+    // Each reprocess request must be tied to a reprocessible session ID.
+    // Valid only for reprocess requests (mIsReprocess == true).
+    private int mReprocessibleSessionId;
 
     private Object mUserTag;
 
@@ -170,6 +173,7 @@
         mSettings = new CameraMetadataNative();
         mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = false;
+        mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     /**
@@ -182,6 +186,7 @@
         mSettings = new CameraMetadataNative(source.mSettings);
         mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
         mIsReprocess = source.mIsReprocess;
+        mReprocessibleSessionId = source.mReprocessibleSessionId;
         mUserTag = source.mUserTag;
     }
 
@@ -189,11 +194,36 @@
      * Take ownership of passed-in settings.
      *
      * Used by the Builder to create a mutable CaptureRequest.
+     *
+     * @param settings Settings for this capture request.
+     * @param isReprocess Indicates whether to create a reprocess capture request. {@code true}
+     *                    to create a reprocess capture request. {@code false} to create a regular
+     *                    capture request.
+     * @param reprocessibleSessionId The ID of the camera capture session this capture is created
+     *                               for. This is used to validate if the application submits a
+     *                               reprocess capture request to the same session where
+     *                               the {@link TotalCaptureResult}, used to create the reprocess
+     *                               capture, came from.
+     *
+     * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+     *                                  reprocessibleSessionId.
+     *
+     * @see CameraDevice#createReprocessCaptureRequest
      */
-    private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) {
+    private CaptureRequest(CameraMetadataNative settings, boolean isReprocess,
+            int reprocessibleSessionId) {
         mSettings = CameraMetadataNative.move(settings);
         mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = isReprocess;
+        if (isReprocess) {
+            if (reprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+                throw new IllegalArgumentException("Create a reprocess capture request with an " +
+                        "invalid session ID: " + reprocessibleSessionId);
+            }
+            mReprocessibleSessionId = reprocessibleSessionId;
+        } else {
+            mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
+        }
     }
 
     /**
@@ -277,6 +307,23 @@
     }
 
     /**
+     * Get the reprocessible session ID this reprocess capture request is associated with.
+     *
+     * @return the reprocessible session ID this reprocess capture request is associated with
+     *
+     * @throws IllegalStateException if this capture request is not a reprocess capture request.
+     * @hide
+     */
+    public int getReprocessibleSessionId() {
+        if (mIsReprocess == false ||
+                mReprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+            throw new IllegalStateException("Getting the reprocessible session ID for a "+
+                    "non-reprocess capture request is illegal.");
+        }
+        return mReprocessibleSessionId;
+    }
+
+    /**
      * Determine whether this CaptureRequest is equal to another CaptureRequest.
      *
      * <p>A request is considered equal to another is if it's set of key/values is equal, it's
@@ -298,7 +345,8 @@
                 && Objects.equals(mUserTag, other.mUserTag)
                 && mSurfaceSet.equals(other.mSurfaceSet)
                 && mSettings.equals(other.mSettings)
-                && mIsReprocess == other.mIsReprocess;
+                && mIsReprocess == other.mIsReprocess
+                && mReprocessibleSessionId == other.mReprocessibleSessionId;
     }
 
     @Override
@@ -347,6 +395,7 @@
         }
 
         mIsReprocess = (in.readInt() == 0) ? false : true;
+        mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     @Override
@@ -397,10 +446,23 @@
          * Initialize the builder using the template; the request takes
          * ownership of the template.
          *
+         * @param template Template settings for this capture request.
+         * @param reprocess Indicates whether to create a reprocess capture request. {@code true}
+         *                  to create a reprocess capture request. {@code false} to create a regular
+         *                  capture request.
+         * @param reprocessibleSessionId The ID of the camera capture session this capture is
+         *                               created for. This is used to validate if the application
+         *                               submits a reprocess capture request to the same session
+         *                               where the {@link TotalCaptureResult}, used to create the
+         *                               reprocess capture, came from.
+         *
+         * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+         *                                  reprocessibleSessionId.
          * @hide
          */
-        public Builder(CameraMetadataNative template, boolean reprocess) {
-            mRequest = new CaptureRequest(template, reprocess);
+        public Builder(CameraMetadataNative template, boolean reprocess,
+                int reprocessibleSessionId) {
+            mRequest = new CaptureRequest(template, reprocess, reprocessibleSessionId);
         }
 
         /**
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 6f7dd78..fb3c098 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -50,6 +50,7 @@
 public final class TotalCaptureResult extends CaptureResult {
 
     private final List<CaptureResult> mPartialResults;
+    private final int mSessionId;
 
     /**
      * Takes ownership of the passed-in camera metadata and the partial results
@@ -58,7 +59,7 @@
      * @hide
      */
     public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
-            CaptureResultExtras extras, List<CaptureResult> partials) {
+            CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) {
         super(results, parent, extras);
 
         if (partials == null) {
@@ -66,6 +67,8 @@
         } else {
             mPartialResults = partials;
         }
+
+        mSessionId = sessionId;
     }
 
     /**
@@ -78,6 +81,7 @@
         super(results, sequenceId);
 
         mPartialResults = new ArrayList<>();
+        mSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     /**
@@ -95,4 +99,14 @@
     public List<CaptureResult> getPartialResults() {
         return Collections.unmodifiableList(mPartialResults);
     }
+
+    /**
+     * Get the ID of the session where the capture request of this result was submitted.
+     *
+     * @return The session ID
+     * @hide
+     */
+    public int getSessionId() {
+        return mSessionId;
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c74204d..3c19529 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -156,9 +156,10 @@
         } else if (request.isReprocess() && !isReprocessible()) {
             throw new IllegalArgumentException("this capture session cannot handle reprocess " +
                     "requests");
+        } else if (request.isReprocess() && request.getReprocessibleSessionId() != mId) {
+            throw new IllegalArgumentException("capture request was created for another session");
         }
 
-
         checkNotClosed();
 
         handler = checkHandler(handler, callback);
@@ -185,12 +186,17 @@
         if (reprocess && !isReprocessible()) {
             throw new IllegalArgumentException("this capture session cannot handle reprocess " +
                     "requests");
+        } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) {
+            throw new IllegalArgumentException("capture request was created for another session");
         }
 
         for (int i = 1; i < requests.size(); i++) {
             if (requests.get(i).isReprocess() != reprocess) {
                 throw new IllegalArgumentException("cannot mix regular and reprocess capture " +
                         " requests");
+            } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) {
+                throw new IllegalArgumentException("capture request was created for another " +
+                    "session");
             }
         }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1e680dfd..ff4ad79 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -585,8 +585,8 @@
                 return null;
             }
 
-            CaptureRequest.Builder builder =
-                    new CaptureRequest.Builder(templatedRequest, /*reprocess*/false);
+            CaptureRequest.Builder builder = new CaptureRequest.Builder(
+                    templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
 
             return builder;
         }
@@ -601,7 +601,8 @@
             CameraMetadataNative resultMetadata = new
                     CameraMetadataNative(inputResult.getNativeCopy());
 
-            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true);
+            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
+                    inputResult.getSessionId());
         }
     }
 
@@ -763,7 +764,7 @@
 
             if (callback != null) {
                 mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
-                        requestList, handler, repeating));
+                        requestList, handler, repeating, mNextSessionId - 1));
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "Listen for request " + requestId + " is null");
@@ -1095,9 +1096,10 @@
         private final CaptureCallback mCallback;
         private final List<CaptureRequest> mRequestList;
         private final Handler mHandler;
+        private final int mSessionId;
 
         CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
-                Handler handler, boolean repeating) {
+                Handler handler, boolean repeating, int sessionId) {
             if (callback == null || handler == null) {
                 throw new UnsupportedOperationException(
                     "Must have a valid handler and a valid callback");
@@ -1106,6 +1108,7 @@
             mHandler = handler;
             mRequestList = new ArrayList<CaptureRequest>(requestList);
             mCallback = callback;
+            mSessionId = sessionId;
         }
 
         public boolean isRepeating() {
@@ -1140,6 +1143,10 @@
             return mHandler;
         }
 
+        public int getSessionId() {
+            return mSessionId;
+        }
+
     }
 
     /**
@@ -1643,8 +1650,8 @@
                     List<CaptureResult> partialResults =
                             mFrameNumberTracker.popPartialResults(frameNumber);
 
-                    final TotalCaptureResult resultAsCapture =
-                            new TotalCaptureResult(result, request, resultExtras, partialResults);
+                    final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
+                            request, resultExtras, partialResults, holder.getSessionId());
 
                     // Final capture result
                     resultDispatch = new Runnable() {
@@ -1665,7 +1672,8 @@
                 holder.getHandler().post(resultDispatch);
 
                 // Collect the partials for a total result; or mark the frame as totally completed
-                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess);
+                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
+                        isReprocess);
 
                 // Fire onCaptureSequenceCompleted
                 if (!isPartialResult) {
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 6f33672..d71b44b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
@@ -170,7 +171,8 @@
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
         assertFalse(metadata.isEmpty());
 
-        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false);
+        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
+                CameraCaptureSession.SESSION_ID_NONE);
         assertFalse(request.isEmpty());
         assertFalse(metadata.isEmpty());
         if (needStream) {