Merge "Parse the last none-empty line of .m3u8 file" into jb-mr2-dev
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 5bdbfbb..115b07c 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -523,7 +523,7 @@
     }
 
     sp<MetaData> params = new MetaData;
-    params->setInt32(kKeyNotRealTime, true);
+    params->setInt32(kKeyRealTimeRecording, false);
     CHECK_EQ(writer->start(params.get()), (status_t)OK);
 
     while (!writer->reachedEOS()) {
diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
index 00f6de3..06fc29d 100644
--- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
+++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
@@ -291,16 +291,30 @@
     {
         Mutex::Autolock lock(mLock);
         ALOGD("MockDrmPlugin::getSecureStops()");
-        const uint8_t ss1[] = {0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89};
-        const uint8_t ss2[] = {0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99};
 
-        Vector<uint8_t> vec;
-        vec.appendArray(ss1, sizeof(ss1));
-        secureStops.push_back(vec);
+        // Properties used in mock test, set by cts test app returned from mock plugin
+        //   byte[] mock-secure-stop1  -> first secure stop in list
+        //   byte[] mock-secure-stop2  -> second secure stop in list
 
-        vec.clear();
-        vec.appendArray(ss2, sizeof(ss2));
-        secureStops.push_back(vec);
+        Vector<uint8_t> ss1, ss2;
+        ssize_t index = mByteArrayProperties.indexOfKey(String8("mock-secure-stop1"));
+        if (index < 0) {
+            ALOGD("Missing 'mock-secure-stop1' parameter for mock");
+            return BAD_VALUE;
+        } else {
+            ss1 = mByteArrayProperties.valueAt(index);
+        }
+
+        index = mByteArrayProperties.indexOfKey(String8("mock-secure-stop2"));
+        if (index < 0) {
+            ALOGD("Missing 'mock-secure-stop2' parameter for mock");
+            return BAD_VALUE;
+        } else {
+            ss2 = mByteArrayProperties.valueAt(index);
+        }
+
+        secureStops.push_back(ss1);
+        secureStops.push_back(ss2);
         return OK;
     }
 
@@ -309,6 +323,11 @@
         Mutex::Autolock lock(mLock);
         ALOGD("MockDrmPlugin::releaseSecureStops(%s)",
               vectorToString(ssRelease).string());
+
+        // Properties used in mock test, set by mock plugin and verifed cts test app
+        //   byte[] secure-stop-release  -> mock-ssrelease
+        mByteArrayProperties.add(String8("mock-ssrelease"), ssRelease);
+
         return OK;
     }
 
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 88df6b0..3ef6b9a 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -74,6 +74,7 @@
 
     int  mFd;
     status_t mInitCheck;
+    bool mIsRealTimeRecording;
     bool mUse4ByteNalLength;
     bool mUse32BitOffset;
     bool mIsFileSizeLimitExplicitlyRequested;
@@ -168,6 +169,13 @@
     // Only makes sense for H.264/AVC
     bool useNalLengthFour();
 
+    // Return whether the writer is used for real time recording.
+    // In real time recording mode, new samples will be allowed to buffered into
+    // chunks in higher priority thread, even though the file writer has not
+    // drained the chunks yet.
+    // By default, real time recording is on.
+    bool isRealTimeRecording() const;
+
     void lock();
     void unlock();
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 9ab3edc..de3fc36 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -112,7 +112,7 @@
     // kKeyTrackTimeStatus is used to track progress in elapsed time
     kKeyTrackTimeStatus   = 'tktm',  // int64_t
 
-    kKeyNotRealTime       = 'ntrt',  // bool (int32_t)
+    kKeyRealTimeRecording = 'rtrc',  // bool (int32_t)
     kKeyNumBuffers        = 'nbbf',  // int32_t
 
     // Ogg files can be tagged to be automatically looping...
diff --git a/libvideoeditor/vss/stagefrightshells/src/VideoEditorUtils.cpp b/libvideoeditor/vss/stagefrightshells/src/VideoEditorUtils.cpp
index 5309bd4..5a7237d 100755
--- a/libvideoeditor/vss/stagefrightshells/src/VideoEditorUtils.cpp
+++ b/libvideoeditor/vss/stagefrightshells/src/VideoEditorUtils.cpp
@@ -189,8 +189,8 @@
     if (meta->findInt64(kKeyTrackTimeStatus, &int64Data)) {
         LOG1("displayMetaData kKeyTrackTimeStatus %lld", int64Data);
     }
-    if (meta->findInt32(kKeyNotRealTime, &int32Data)) {
-        LOG1("displayMetaData kKeyNotRealTime %d", int32Data);
+    if (meta->findInt32(kKeyRealTimeRecording, &int32Data)) {
+        LOG1("displayMetaData kKeyRealTimeRecording %d", int32Data);
     }
 }
 
diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp
index 1578846..902aeb2 100644
--- a/media/libmedia/IDrm.cpp
+++ b/media/libmedia/IDrm.cpp
@@ -590,6 +590,7 @@
                 size_t size = iter->size();
                 reply->writeInt32(size);
                 reply->write(iter->array(), iter->size());
+                iter++;
             }
             reply->writeInt32(result);
             return OK;
diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
index a5ff0ca..50ebf9c 100644
--- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp
@@ -66,7 +66,9 @@
 }
 
 NuPlayer::RTSPSource::~RTSPSource() {
-   mLooper->stop();
+    if (mLooper != NULL) {
+        mLooper->stop();
+    }
 }
 
 void NuPlayer::RTSPSource::prepareAsync() {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 316f669..a0f17b5 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -212,7 +212,6 @@
     int64_t mTrackDurationUs;
     int64_t mMaxChunkDurationUs;
 
-    bool mIsRealTimeRecording;
     int64_t mEstimatedTrackSizeBytes;
     int64_t mMdatSizeBytes;
     int32_t mTimeScale;
@@ -335,6 +334,7 @@
 MPEG4Writer::MPEG4Writer(const char *filename)
     : mFd(-1),
       mInitCheck(NO_INIT),
+      mIsRealTimeRecording(true),
       mUse4ByteNalLength(true),
       mUse32BitOffset(true),
       mIsFileSizeLimitExplicitlyRequested(false),
@@ -359,6 +359,7 @@
 MPEG4Writer::MPEG4Writer(int fd)
     : mFd(dup(fd)),
       mInitCheck(mFd < 0? NO_INIT: OK),
+      mIsRealTimeRecording(true),
       mUse4ByteNalLength(true),
       mUse32BitOffset(true),
       mIsFileSizeLimitExplicitlyRequested(false),
@@ -596,6 +597,11 @@
         mUse4ByteNalLength = false;
     }
 
+    int32_t isRealTimeRecording;
+    if (param && param->findInt32(kKeyRealTimeRecording, &isRealTimeRecording)) {
+        mIsRealTimeRecording = isRealTimeRecording;
+    }
+
     mStartTimestampUs = -1;
 
     if (mStarted) {
@@ -1640,12 +1646,18 @@
             mChunkReadyCondition.wait(mLock);
         }
 
-        // Actual write without holding the lock in order to
-        // reduce the blocking time for media track threads.
+        // In real time recording mode, write without holding the lock in order
+        // to reduce the blocking time for media track threads.
+        // Otherwise, hold the lock until the existing chunks get written to the
+        // file.
         if (chunkFound) {
-            mLock.unlock();
+            if (mIsRealTimeRecording) {
+                mLock.unlock();
+            }
             writeChunkToFile(&chunk);
-            mLock.lock();
+            if (mIsRealTimeRecording) {
+                mLock.lock();
+            }
         }
     }
 
@@ -1695,18 +1707,10 @@
         mRotation = rotationDegrees;
     }
 
-    mIsRealTimeRecording = true;
-    {
-        int32_t isNotRealTime;
-        if (params && params->findInt32(kKeyNotRealTime, &isNotRealTime)) {
-            mIsRealTimeRecording = (isNotRealTime == 0);
-        }
-    }
-
     initTrackingProgressStatus(params);
 
     sp<MetaData> meta = new MetaData;
-    if (mIsRealTimeRecording && mOwner->numTracks() > 1) {
+    if (mOwner->isRealTimeRecording() && mOwner->numTracks() > 1) {
         /*
          * This extra delay of accepting incoming audio/video signals
          * helps to align a/v start time at the beginning of a recording
@@ -2084,7 +2088,10 @@
     } else {
         prctl(PR_SET_NAME, (unsigned long)"VideoTrackEncoding", 0, 0, 0);
     }
-    androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
+
+    if (mOwner->isRealTimeRecording()) {
+        androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
+    }
 
     sp<MetaData> meta_data;
 
@@ -2245,7 +2252,7 @@
 
         }
 
-        if (mIsRealTimeRecording) {
+        if (mOwner->isRealTimeRecording()) {
             if (mIsAudio) {
                 updateDriftTime(meta_data);
             }
@@ -2531,6 +2538,10 @@
     return mDriftTimeUs;
 }
 
+bool MPEG4Writer::isRealTimeRecording() const {
+    return mIsRealTimeRecording;
+}
+
 bool MPEG4Writer::useNalLengthFour() {
     return mUse4ByteNalLength;
 }
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index b948fe2..388c65b 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -107,6 +107,7 @@
     Mutex::Autolock autoLock(mMuxerLock);
     if (mState == INITIALIZED) {
         mState = STARTED;
+        mFileMeta->setInt32(kKeyRealTimeRecording, false);
         return mWriter->start(mFileMeta.get());
     } else {
         ALOGE("start() is called in invalid state %d", mState);
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
index 8ba2afb..cf81c16 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
+++ b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
@@ -29,6 +29,7 @@
 
 #define DRC_DEFAULT_MOBILE_REF_LEVEL 64  /* 64*-0.25dB = -16 dB below full scale for mobile conf */
 #define DRC_DEFAULT_MOBILE_DRC_CUT   127 /* maximum compression of dynamic range for mobile conf */
+#define DRC_DEFAULT_MOBILE_DRC_BOOST 127 /* maximum compression of dynamic range for mobile conf */
 #define MAX_CHANNEL_COUNT            6  /* maximum number of audio channels that can be decoded */
 // names of properties that can be used to override the default DRC settings
 #define PROP_DRC_OVERRIDE_REF_LEVEL  "aac_drc_reference_level"
@@ -146,6 +147,8 @@
         unsigned boost = atoi(value);
         ALOGV("AAC decoder using AAC_DRC_BOOST_FACTOR of %d", boost);
         aacDecoder_SetParam(mAACDecoder, AAC_DRC_BOOST_FACTOR, boost);
+    } else {
+        aacDecoder_SetParam(mAACDecoder, AAC_DRC_BOOST_FACTOR, DRC_DEFAULT_MOBILE_DRC_BOOST);
     }
 
     return status;
@@ -592,6 +595,12 @@
 
 void SoftAAC2::onReset() {
     drainDecoder();
+    // reset the "configured" state
+    mInputBufferCount = 0;
+    mNumSamplesOutput = 0;
+    // To make the codec behave the same before and after a reset, we need to invalidate the
+    // streaminfo struct. This does that:
+    mStreamInfo->sampleRate = 0;
 }
 
 void SoftAAC2::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
diff --git a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
index 922ac61..4115324 100644
--- a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
+++ b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
@@ -411,8 +411,19 @@
 }
 
 void SoftVorbis::onReset() {
+    mInputBufferCount = 0;
     mNumFramesOutput = 0;
-    vorbis_dsp_restart(mState);
+    if (mState != NULL) {
+        vorbis_dsp_clear(mState);
+        delete mState;
+        mState = NULL;
+    }
+
+    if (mVi != NULL) {
+        vorbis_info_clear(mVi);
+        delete mVi;
+        mVi = NULL;
+    }
 }
 
 void SoftVorbis::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
diff --git a/services/audioflinger/ISchedulingPolicyService.cpp b/services/audioflinger/ISchedulingPolicyService.cpp
index 909b77e..218aa6b 100644
--- a/services/audioflinger/ISchedulingPolicyService.cpp
+++ b/services/audioflinger/ISchedulingPolicyService.cpp
@@ -44,7 +44,7 @@
         data.writeInt32(pid);
         data.writeInt32(tid);
         data.writeInt32(prio);
-        remote()->transact(REQUEST_PRIORITY_TRANSACTION, data, &reply);
+        remote()->transact(REQUEST_PRIORITY_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
         // fail on exception
         if (reply.readExceptionCode() != 0) return -1;
         return reply.readInt32();
diff --git a/services/camera/libcameraservice/Camera3Device.cpp b/services/camera/libcameraservice/Camera3Device.cpp
index 08aef83..d67b535 100644
--- a/services/camera/libcameraservice/Camera3Device.cpp
+++ b/services/camera/libcameraservice/Camera3Device.cpp
@@ -51,6 +51,8 @@
         mId(id),
         mHal3Device(NULL),
         mStatus(STATUS_UNINITIALIZED),
+        mNextResultFrameNumber(0),
+        mNextShutterFrameNumber(0),
         mListener(NULL)
 {
     ATRACE_CALL();
@@ -246,8 +248,22 @@
         mOutputStreams[i]->dump(fd,args);
     }
 
+    lines = String8("    In-flight requests:\n");
+    if (mInFlightMap.size() == 0) {
+        lines.append("      None\n");
+    } else {
+        for (size_t i = 0; i < mInFlightMap.size(); i++) {
+            InFlightRequest r = mInFlightMap.valueAt(i);
+            lines.appendFormat("      Frame %d |  Timestamp: %lld, metadata"
+                    " arrived: %s, buffers left: %d\n", mInFlightMap.keyAt(i),
+                    r.captureTimestamp, r.haveResultMetadata ? "true" : "false",
+                    r.numBuffersLeft);
+        }
+    }
+    write(fd, lines.string(), lines.size());
+
     if (mHal3Device != NULL) {
-        lines = String8("     HAL device dump:\n");
+        lines = String8("    HAL device dump:\n");
         write(fd, lines.string(), lines.size());
         mHal3Device->ops->dump(mHal3Device, fd);
     }
@@ -927,15 +943,36 @@
 }
 
 void Camera3Device::setErrorStateLockedV(const char *fmt, va_list args) {
-    // Only accept the first failure cause
+    // Print out all error messages to log
+    String8 errorCause = String8::formatV(fmt, args);
+    ALOGE("Camera %d: %s", mId, errorCause.string());
+
+    // But only do error state transition steps for the first error
     if (mStatus == STATUS_ERROR) return;
 
-    mErrorCause = String8::formatV(fmt, args);
-    ALOGE("Camera %d: %s", mId, mErrorCause.string());
+    mErrorCause = errorCause;
+
+    mRequestThread->setPaused(true);
     mStatus = STATUS_ERROR;
 }
 
 /**
+ * In-flight request management
+ */
+
+status_t Camera3Device::registerInFlight(int32_t frameNumber,
+        int32_t numBuffers) {
+    ATRACE_CALL();
+    Mutex::Autolock l(mInFlightLock);
+
+    ssize_t res;
+    res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers));
+    if (res < 0) return res;
+
+    return OK;
+}
+
+/**
  * Camera HAL device callback methods
  */
 
@@ -944,47 +981,107 @@
 
     status_t res;
 
-    if (result->result == NULL) {
-        SET_ERR("No metadata provided by HAL for frame %d",
-                result->frame_number);
+    uint32_t frameNumber = result->frame_number;
+    if (result->result == NULL && result->num_output_buffers == 0) {
+        SET_ERR("No result data provided by HAL for frame %d",
+                frameNumber);
         return;
     }
 
+    // Get capture timestamp from list of in-flight requests, where it was added
+    // by the shutter notification for this frame. Then update the in-flight
+    // status and remove the in-flight entry if all result data has been
+    // received.
     nsecs_t timestamp = 0;
+    {
+        Mutex::Autolock l(mInFlightLock);
+        ssize_t idx = mInFlightMap.indexOfKey(frameNumber);
+        if (idx == NAME_NOT_FOUND) {
+            SET_ERR("Unknown frame number for capture result: %d",
+                    frameNumber);
+            return;
+        }
+        InFlightRequest &request = mInFlightMap.editValueAt(idx);
+        timestamp = request.captureTimestamp;
+        if (timestamp == 0) {
+            SET_ERR("Called before shutter notify for frame %d",
+                    frameNumber);
+            return;
+        }
+
+        if (result->result != NULL) {
+            if (request.haveResultMetadata) {
+                SET_ERR("Called multiple times with metadata for frame %d",
+                        frameNumber);
+                return;
+            }
+            request.haveResultMetadata = true;
+        }
+
+        request.numBuffersLeft -= result->num_output_buffers;
+
+        if (request.numBuffersLeft < 0) {
+            SET_ERR("Too many buffers returned for frame %d",
+                    frameNumber);
+            return;
+        }
+
+        if (request.haveResultMetadata && request.numBuffersLeft == 0) {
+            mInFlightMap.removeItemsAt(idx, 1);
+        }
+
+        // Sanity check - if we have too many in-flight frames, something has
+        // likely gone wrong
+        if (mInFlightMap.size() > kInFlightWarnLimit) {
+            CLOGE("In-flight list too large: %d", mInFlightMap.size());
+        }
+
+    }
+
     AlgState cur3aState;
     AlgState new3aState;
     int32_t aeTriggerId = 0;
     int32_t afTriggerId = 0;
 
-    NotificationListener *listener;
+    NotificationListener *listener = NULL;
 
-    {
+    // Process the result metadata, if provided
+    if (result->result != NULL) {
         Mutex::Autolock l(mOutputLock);
 
-        // Push result metadata into queue
-        mResultQueue.push_back(CameraMetadata());
-        // Lets avoid copies! Too bad there's not a #back method
-        CameraMetadata &captureResult = *(--mResultQueue.end());
+        if (frameNumber != mNextResultFrameNumber) {
+            SET_ERR("Out-of-order capture result metadata submitted! "
+                    "(got frame number %d, expecting %d)",
+                    frameNumber, mNextResultFrameNumber);
+            return;
+        }
+        mNextResultFrameNumber++;
+
+        CameraMetadata &captureResult =
+                *mResultQueue.insert(mResultQueue.end(), CameraMetadata());
 
         captureResult = result->result;
         if (captureResult.update(ANDROID_REQUEST_FRAME_COUNT,
-                        (int32_t*)&result->frame_number, 1) != OK) {
+                        (int32_t*)&frameNumber, 1) != OK) {
             SET_ERR("Failed to set frame# in metadata (%d)",
-                    result->frame_number);
+                    frameNumber);
         } else {
             ALOGVV("%s: Camera %d: Set frame# in metadata (%d)",
-                    __FUNCTION__, mId, result->frame_number);
+                    __FUNCTION__, mId, frameNumber);
         }
 
-        // Get timestamp from result metadata
+        // Check that there's a timestamp in the result metadata
 
         camera_metadata_entry entry =
                 captureResult.find(ANDROID_SENSOR_TIMESTAMP);
         if (entry.count == 0) {
             SET_ERR("No timestamp provided by HAL for frame %d!",
-                    result->frame_number);
-        } else {
-            timestamp = entry.data.i64[0];
+                    frameNumber);
+        }
+        if (timestamp != entry.data.i64[0]) {
+            SET_ERR("Timestamp mismatch between shutter notify and result"
+                    " metadata for frame %d (%lld vs %lld respectively)",
+                    frameNumber, timestamp, entry.data.i64[0]);
         }
 
         // Get 3A states from result metadata
@@ -992,7 +1089,7 @@
         entry = captureResult.find(ANDROID_CONTROL_AE_STATE);
         if (entry.count == 0) {
             CLOGE("No AE state provided by HAL for frame %d!",
-                    result->frame_number);
+                    frameNumber);
         } else {
             new3aState.aeState =
                     static_cast<camera_metadata_enum_android_control_ae_state>(
@@ -1002,7 +1099,7 @@
         entry = captureResult.find(ANDROID_CONTROL_AF_STATE);
         if (entry.count == 0) {
             CLOGE("No AF state provided by HAL for frame %d!",
-                    result->frame_number);
+                    frameNumber);
         } else {
             new3aState.afState =
                     static_cast<camera_metadata_enum_android_control_af_state>(
@@ -1012,7 +1109,7 @@
         entry = captureResult.find(ANDROID_CONTROL_AWB_STATE);
         if (entry.count == 0) {
             CLOGE("No AWB state provided by HAL for frame %d!",
-                    result->frame_number);
+                    frameNumber);
         } else {
             new3aState.awbState =
                     static_cast<camera_metadata_enum_android_control_awb_state>(
@@ -1022,7 +1119,7 @@
         entry = captureResult.find(ANDROID_CONTROL_AF_TRIGGER_ID);
         if (entry.count == 0) {
             CLOGE("No AF trigger ID provided by HAL for frame %d!",
-                    result->frame_number);
+                    frameNumber);
         } else {
             afTriggerId = entry.data.i32[0];
         }
@@ -1030,7 +1127,7 @@
         entry = captureResult.find(ANDROID_CONTROL_AE_PRECAPTURE_ID);
         if (entry.count == 0) {
             CLOGE("No AE precapture trigger ID provided by HAL"
-                    " for frame %d!", result->frame_number);
+                    " for frame %d!", frameNumber);
         } else {
             aeTriggerId = entry.data.i32[0];
         }
@@ -1041,7 +1138,8 @@
         m3AState = new3aState;
     } // scope for mOutputLock
 
-    // Return completed buffers to their streams
+    // Return completed buffers to their streams with the timestamp
+
     for (size_t i = 0; i < result->num_output_buffers; i++) {
         Camera3Stream *stream =
                 Camera3Stream::cast(result->output_buffers[i].stream);
@@ -1050,20 +1148,21 @@
         // last reference to it.
         if (res != OK) {
             SET_ERR("Can't return buffer %d for frame %d to its stream: "
-                    " %s (%d)", i, result->frame_number, strerror(-res), res);
+                    " %s (%d)", i, frameNumber, strerror(-res), res);
         }
     }
 
-    // Dispatch any 3A change events to listeners
-    if (listener != NULL) {
+    // Finally, dispatch any 3A change events to listeners if we got metadata
+
+    if (result->result != NULL && listener != NULL) {
         if (new3aState.aeState != cur3aState.aeState) {
             ALOGVV("%s: AE state changed from 0x%x to 0x%x",
-                   __FUNCTION__, cur3aState.aeState, new3aState.aeState);
+                    __FUNCTION__, cur3aState.aeState, new3aState.aeState);
             listener->notifyAutoExposure(new3aState.aeState, aeTriggerId);
         }
         if (new3aState.afState != cur3aState.afState) {
             ALOGVV("%s: AF state changed from 0x%x to 0x%x",
-                   __FUNCTION__, cur3aState.afState, new3aState.afState);
+                    __FUNCTION__, cur3aState.afState, new3aState.afState);
             listener->notifyAutoFocus(new3aState.afState, afTriggerId);
         }
         if (new3aState.awbState != cur3aState.awbState) {
@@ -1077,12 +1176,11 @@
     NotificationListener *listener;
     {
         Mutex::Autolock l(mOutputLock);
-        if (mListener == NULL) return;
         listener = mListener;
     }
 
     if (msg == NULL) {
-        SET_ERR_L("HAL sent NULL notify message!");
+        SET_ERR("HAL sent NULL notify message!");
         return;
     }
 
@@ -1095,17 +1193,50 @@
                                   msg->message.error.error_stream);
                 streamId = stream->getId();
             }
-            listener->notifyError(msg->message.error.error_code,
-                    msg->message.error.frame_number, streamId);
+            if (listener != NULL) {
+                listener->notifyError(msg->message.error.error_code,
+                        msg->message.error.frame_number, streamId);
+            }
             break;
         }
         case CAMERA3_MSG_SHUTTER: {
-            listener->notifyShutter(msg->message.shutter.frame_number,
-                    msg->message.shutter.timestamp);
+            ssize_t idx;
+            uint32_t frameNumber = msg->message.shutter.frame_number;
+            nsecs_t timestamp = msg->message.shutter.timestamp;
+            // Verify ordering of shutter notifications
+            {
+                Mutex::Autolock l(mOutputLock);
+                if (frameNumber != mNextShutterFrameNumber) {
+                    SET_ERR("Shutter notification out-of-order. Expected "
+                            "notification for frame %d, got frame %d",
+                            mNextShutterFrameNumber, frameNumber);
+                    break;
+                }
+                mNextShutterFrameNumber++;
+            }
+
+            // Set timestamp for the request in the in-flight tracking
+            {
+                Mutex::Autolock l(mInFlightLock);
+                idx = mInFlightMap.indexOfKey(frameNumber);
+                if (idx >= 0) {
+                    mInFlightMap.editValueAt(idx).captureTimestamp = timestamp;
+                }
+            }
+            if (idx < 0) {
+                SET_ERR("Shutter notification for non-existent frame number %d",
+                        frameNumber);
+                break;
+            }
+
+            // Call listener, if any
+            if (listener != NULL) {
+                listener->notifyShutter(frameNumber, timestamp);
+            }
             break;
         }
         default:
-            SET_ERR_L("Unknown notify message from HAL: %d",
+            SET_ERR("Unknown notify message from HAL: %d",
                     msg->type);
     }
 }
@@ -1119,6 +1250,7 @@
         Thread(false),
         mParent(parent),
         mHal3Device(hal3Device),
+        mId(getId(parent)),
         mReconfigured(false),
         mDoPause(false),
         mPaused(true),
@@ -1158,6 +1290,12 @@
     return OK;
 }
 
+int Camera3Device::RequestThread::getId(const wp<Camera3Device> &device) {
+    sp<Camera3Device> d = device.promote();
+    if (d != NULL) return d->mId;
+    return 0;
+}
+
 status_t Camera3Device::RequestThread::queueTriggerLocked(
         RequestTrigger trigger) {
 
@@ -1170,9 +1308,8 @@
         case TYPE_INT32:
             break;
         default:
-            ALOGE("%s: Type not supported: 0x%x",
-                  __FUNCTION__,
-                  trigger.getTagType());
+            ALOGE("%s: Type not supported: 0x%x", __FUNCTION__,
+                    trigger.getTagType());
             return INVALID_OPERATION;
     }
 
@@ -1340,6 +1477,22 @@
 
     request.frame_number = mFrameNumber++;
 
+    // Log request in the in-flight queue
+    sp<Camera3Device> parent = mParent.promote();
+    if (parent == NULL) {
+        CLOGE("RequestThread: Parent is gone");
+        cleanUpFailedRequest(request, nextRequest, outputBuffers);
+        return false;
+    }
+
+    res = parent->registerInFlight(request.frame_number,
+            request.num_output_buffers);
+    if (res != OK) {
+        SET_ERR("RequestThread: Unable to register new in-flight request:"
+                " %s (%d)", strerror(-res), res);
+        cleanUpFailedRequest(request, nextRequest, outputBuffers);
+        return false;
+    }
 
     // Submit request and block until ready for next one
 
diff --git a/services/camera/libcameraservice/Camera3Device.h b/services/camera/libcameraservice/Camera3Device.h
index 2e4a303..6cad08e 100644
--- a/services/camera/libcameraservice/Camera3Device.h
+++ b/services/camera/libcameraservice/Camera3Device.h
@@ -108,7 +108,8 @@
             buffer_handle_t *buffer, wp<BufferReleasedListener> listener);
 
   private:
-    static const nsecs_t       kShutdownTimeout = 5000000000; // 5 sec
+    static const size_t        kInFlightWarnLimit = 20;
+    static const nsecs_t       kShutdownTimeout   = 5000000000; // 5 sec
     struct                     RequestTrigger;
 
     Mutex                      mLock;
@@ -262,6 +263,8 @@
         virtual bool threadLoop();
 
       private:
+        static int         getId(const wp<Camera3Device> &device);
+
         status_t           queueTriggerLocked(RequestTrigger trigger);
         // Mix-in queued triggers into this request
         int32_t            insertTriggers(const sp<CaptureRequest> &request);
@@ -291,6 +294,8 @@
         wp<Camera3Device>  mParent;
         camera3_device_t  *mHal3Device;
 
+        const int          mId;
+
         Mutex              mRequestLock;
         Condition          mRequestSignal;
         RequestList        mRequestQueue;
@@ -308,7 +313,7 @@
         sp<CaptureRequest> mPrevRequest;
         int32_t            mPrevTriggers;
 
-        int32_t            mFrameNumber;
+        uint32_t           mFrameNumber;
 
         Mutex              mLatestRequestMutex;
         Condition          mLatestRequestSignal;
@@ -324,6 +329,39 @@
     sp<RequestThread> mRequestThread;
 
     /**
+     * In-flight queue for tracking completion of capture requests.
+     */
+
+    struct InFlightRequest {
+        // Set by notify() SHUTTER call.
+        nsecs_t captureTimestamp;
+        // Set by process_capture_result call with valid metadata
+        bool    haveResultMetadata;
+        // Decremented by calls to process_capture_result with valid output
+        // buffers
+        int     numBuffersLeft;
+
+        InFlightRequest() :
+                captureTimestamp(0),
+                haveResultMetadata(false),
+                numBuffersLeft(0) {
+        }
+
+        explicit InFlightRequest(int numBuffers) :
+                captureTimestamp(0),
+                haveResultMetadata(false),
+                numBuffersLeft(numBuffers) {
+        }
+    };
+    // Map from frame number to the in-flight request state
+    typedef KeyedVector<uint32_t, InFlightRequest> InFlightMap;
+
+    Mutex                  mInFlightLock; // Protects mInFlightMap
+    InFlightMap            mInFlightMap;
+
+    status_t registerInFlight(int32_t frameNumber, int32_t numBuffers);
+
+    /**
      * Output result queue and current HAL device 3A state
      */
 
@@ -332,6 +370,8 @@
 
     /**** Scope for mOutputLock ****/
 
+    uint32_t               mNextResultFrameNumber;
+    uint32_t               mNextShutterFrameNumber;
     List<CameraMetadata>   mResultQueue;
     Condition              mResultSignal;
     NotificationListener  *mListener;