Merge "Suppress empty media.metrics records from nuplayer" into oc-dev
diff --git a/camera/include/camera/ndk/NdkCameraMetadataTags.h b/camera/include/camera/ndk/NdkCameraMetadataTags.h
index 25d364e..8b76cdf 100644
--- a/camera/include/camera/ndk/NdkCameraMetadataTags.h
+++ b/camera/include/camera/ndk/NdkCameraMetadataTags.h
@@ -1547,6 +1547,7 @@
      * <code>false</code> if present.</p>
      * <p>For applications targeting SDK versions older than O, the value of enableZsl in all
      * capture templates is always <code>false</code> if present.</p>
+     * <p>For application-operated ZSL, use CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG template.</p>
      *
      * @see ACAMERA_CONTROL_CAPTURE_INTENT
      * @see ACAMERA_SENSOR_TIMESTAMP
diff --git a/include/media/Interpolator.h b/include/media/Interpolator.h
index 120a074..45a0585 100644
--- a/include/media/Interpolator.h
+++ b/include/media/Interpolator.h
@@ -152,8 +152,8 @@
                         : mLastSlope;
             } else {
                 // finite difference spline
-                m0 = (sec0 + sec) * 0.5;
-                m1 = (sec1 + sec) * 0.5;
+                m0 = (sec0 + sec) * 0.5f;
+                m1 = (sec1 + sec) * 0.5f;
             }
 
             if (monotonic) {
@@ -294,12 +294,21 @@
 
     std::string toString() const {
         std::stringstream ss;
-        ss << "mInterpolatorType: " << mInterpolatorType << std::endl;
-        ss << "mFirstSlope: " << mFirstSlope << std::endl;
-        ss << "mLastSlope: " << mLastSlope << std::endl;
+        ss << "Interpolator{mInterpolatorType=" << static_cast<int32_t>(mInterpolatorType);
+        ss << ", mFirstSlope=" << mFirstSlope;
+        ss << ", mLastSlope=" << mLastSlope;
+        ss << ", {";
+        bool first = true;
         for (const auto &pt : *this) {
-            ss << pt.first << " " << pt.second << std::endl;
+            if (first) {
+                first = false;
+                ss << "{";
+            } else {
+                ss << ", {";
+            }
+            ss << pt.first << ", " << pt.second << "}";
         }
+        ss << "}}";
         return ss.str();
     }
 
@@ -324,7 +333,7 @@
 
     // spline cubic polynomial coefficient cache
     std::unordered_map<S, std::tuple<S /* c1 */, S /* c2 */, S /* c3 */>> mMemo;
-};
+}; // Interpolator
 
 } // namespace android
 
diff --git a/include/media/VolumeShaper.h b/include/media/VolumeShaper.h
index 1282124..e4c0b5b 100644
--- a/include/media/VolumeShaper.h
+++ b/include/media/VolumeShaper.h
@@ -51,31 +51,68 @@
 
 class VolumeShaper {
 public:
-    using S = float;
-    using T = float;
+    // S and T are like template typenames (matching the Interpolator<S, T>)
+    using S = float; // time type
+    using T = float; // volume type
 
-    static const int kSystemIdMax = 16;
+// Curve and dimension information
+// TODO: member static const or constexpr float initialization not permitted in C++11
+#define MIN_CURVE_TIME    0.f  // type S: start of VolumeShaper curve (normalized)
+#define MAX_CURVE_TIME    1.f  // type S: end of VolumeShaper curve (normalized)
+#define MIN_LINEAR_VOLUME 0.f  // type T: silence / mute audio
+#define MAX_LINEAR_VOLUME 1.f  // type T: max volume, unity gain
+#define MAX_LOG_VOLUME    0.f  // type T: max volume, unity gain in dBFS
 
-    // VolumeShaper::Status is equivalent to status_t if negative
-    // but if non-negative represents the id operated on.
-    // It must be expressible as an int32_t for binder purposes.
+    /* kSystemVolumeShapersMax is the maximum number of system VolumeShapers.
+     * Each system VolumeShapers has a predefined Id, which ranges from 0
+     * to kSystemVolumeShapersMax - 1 and is unique for its usage.
+     *
+     * "1" is reserved for system ducking.
+     */
+    static const int kSystemVolumeShapersMax = 16;
+
+    /* kUserVolumeShapersMax is the maximum number of application
+     * VolumeShapers for a player/track.  Application VolumeShapers are
+     * assigned on creation by the client, and have Ids ranging
+     * from kSystemVolumeShapersMax to INT32_MAX.
+     *
+     * The number of user/application volume shapers is independent to the
+     * system volume shapers. If an application tries to create more than
+     * kUserVolumeShapersMax to a player, then the apply() will fail.
+     * This prevents exhausting server side resources by a potentially malicious
+     * application.
+     */
+    static const int kUserVolumeShapersMax = 16;
+
+    /* VolumeShaper::Status is equivalent to status_t if negative
+     * but if non-negative represents the id operated on.
+     * It must be expressible as an int32_t for binder purposes.
+     */
     using Status = status_t;
 
+    // Local definition for clamp as std::clamp is included in C++17 only.
+    // TODO: use the std::clamp version when Android build uses C++17.
+    template<typename R>
+    static constexpr const R &clamp(const R &v, const R &lo, const R &hi) {
+        return (v < lo) ? lo : (hi < v) ? hi : v;
+    }
+
+    /* VolumeShaper.Configuration derives from the Interpolator class and adds
+     * parameters relating to the volume shape.
+     *
+     * This parallels the Java implementation and the enums must match.
+     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
+     * details on the Java implementation.
+     */
     class Configuration : public Interpolator<S, T>, public RefBase {
     public:
-        /* VolumeShaper.Configuration derives from the Interpolator class and adds
-         * parameters relating to the volume shape.
-         */
-
-        // TODO document as per VolumeShaper.java flags.
-
-        // must match with VolumeShaper.java in frameworks/base
+        // Must match with VolumeShaper.java in frameworks/base.
         enum Type : int32_t {
             TYPE_ID,
             TYPE_SCALE,
         };
 
-        // must match with VolumeShaper.java in frameworks/base
+        // Must match with VolumeShaper.java in frameworks/base.
         enum OptionFlag : int32_t {
             OPTION_FLAG_NONE           = 0,
             OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0),
@@ -84,11 +121,12 @@
             OPTION_FLAG_ALL            = (OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME),
         };
 
-        // bring to derived class; must match with VolumeShaper.java in frameworks/base
+        // Bring from base class; must match with VolumeShaper.java in frameworks/base.
         using InterpolatorType = Interpolator<S, T>::InterpolatorType;
 
         Configuration()
             : Interpolator<S, T>()
+            , RefBase()
             , mType(TYPE_SCALE)
             , mOptionFlags(OPTION_FLAG_NONE)
             , mDurationMs(1000.)
@@ -97,6 +135,7 @@
 
         explicit Configuration(const Configuration &configuration)
             : Interpolator<S, T>(*static_cast<const Interpolator<S, T> *>(&configuration))
+            , RefBase()
             , mType(configuration.mType)
             , mOptionFlags(configuration.mOptionFlags)
             , mDurationMs(configuration.mDurationMs)
@@ -136,8 +175,13 @@
             return mDurationMs;
         }
 
-        void setDurationMs(double durationMs) {
-            mDurationMs = durationMs;
+        status_t setDurationMs(double durationMs) {
+            if (durationMs > 0.) {
+                mDurationMs = durationMs;
+                return NO_ERROR;
+            }
+            // zero, negative, or nan. These values not possible from Java.
+            return BAD_VALUE;
         }
 
         int32_t getId() const {
@@ -145,45 +189,46 @@
         }
 
         void setId(int32_t id) {
+            // We permit a negative id here (representing invalid).
             mId = id;
         }
 
+        /* Adjust the volume to be in linear range from MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME
+         * and compensate for log dbFS volume as needed.
+         */
         T adjustVolume(T volume) const {
             if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
-                const T out = powf(10.f, volume / 10.);
+                const T out = powf(10.f, volume / 10.f);
                 VS_LOG("in: %f  out: %f", volume, out);
                 volume = out;
             }
-            // clamp
-            if (volume < 0.f) {
-                volume = 0.f;
-            } else if (volume > 1.f) {
-                volume = 1.f;
-            }
-            return volume;
+            return clamp(volume, MIN_LINEAR_VOLUME /* lo */, MAX_LINEAR_VOLUME /* hi */);
         }
 
-        status_t checkCurve() {
+        /* Check if the existing curve is valid.
+         */
+        status_t checkCurve() const {
             if (mType == TYPE_ID) return NO_ERROR;
             if (this->size() < 2) {
                 ALOGE("curve must have at least 2 points");
                 return BAD_VALUE;
             }
-            if (first().first != 0.f || last().first != 1.f) {
-                ALOGE("curve must start at 0.f and end at 1.f");
+            if (first().first != MIN_CURVE_TIME || last().first != MAX_CURVE_TIME) {
+                ALOGE("curve must start at MIN_CURVE_TIME and end at MAX_CURVE_TIME");
                 return BAD_VALUE;
             }
             if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
                 for (const auto &pt : *this) {
-                    if (!(pt.second <= 0.f) /* handle nan */) {
+                    if (!(pt.second <= MAX_LOG_VOLUME) /* handle nan */) {
                         ALOGE("positive volume dbFS");
                         return BAD_VALUE;
                     }
                 }
             } else {
                 for (const auto &pt : *this) {
-                    if (!(pt.second >= 0.f) || !(pt.second <= 1.f) /* handle nan */) {
-                        ALOGE("volume < 0.f or > 1.f");
+                    if (!(pt.second >= MIN_LINEAR_VOLUME)
+                            || !(pt.second <= MAX_LINEAR_VOLUME) /* handle nan */) {
+                        ALOGE("volume < MIN_LINEAR_VOLUME or > MAX_LINEAR_VOLUME");
                         return BAD_VALUE;
                     }
                 }
@@ -191,19 +236,22 @@
             return NO_ERROR;
         }
 
+        /* Clamps the volume curve in the configuration to
+         * the valid range for log or linear scale.
+         */
         void clampVolume() {
             if ((mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
                 for (auto it = this->begin(); it != this->end(); ++it) {
-                    if (!(it->second <= 0.f) /* handle nan */) {
-                        it->second = 0.f;
+                    if (!(it->second <= MAX_LOG_VOLUME) /* handle nan */) {
+                        it->second = MAX_LOG_VOLUME;
                     }
                 }
             } else {
                 for (auto it = this->begin(); it != this->end(); ++it) {
-                    if (!(it->second >= 0.f) /* handle nan */) {
-                        it->second = 0.f;
-                    } else if (!(it->second <= 1.f)) {
-                        it->second = 1.f;
+                    if (!(it->second >= MIN_LINEAR_VOLUME) /* handle nan */) {
+                        it->second = MIN_LINEAR_VOLUME;
+                    } else if (!(it->second <= MAX_LINEAR_VOLUME)) {
+                        it->second = MAX_LINEAR_VOLUME;
                     }
                 }
             }
@@ -224,8 +272,9 @@
             if (endVolume == startVolume) {
                 // match with linear ramp
                 const T offset = volume - startVolume;
+                static const T scale =  1.f / (MAX_CURVE_TIME - MIN_CURVE_TIME); // nominally 1.f
                 for (auto it = this->begin(); it != this->end(); ++it) {
-                    it->second = it->second + offset * (1.f - it->first);
+                    it->second = it->second + offset * (MAX_CURVE_TIME - it->first) * scale;
                 }
             } else {
                 const T  scale = (volume - endVolume) / (startVolume - endVolume);
@@ -262,32 +311,40 @@
                             ?: checkCurve();
         }
 
+        // Returns a string for debug printing.
         std::string toString() const {
             std::stringstream ss;
-            ss << "mType: " << mType << std::endl;
-            ss << "mId: " << mId << std::endl;
+            ss << "VolumeShaper::Configuration{mType=" << static_cast<int32_t>(mType);
+            ss << ", mId=" << mId;
             if (mType != TYPE_ID) {
-                ss << "mOptionFlags: " << mOptionFlags << std::endl;
-                ss << "mDurationMs: " << mDurationMs << std::endl;
-                ss << Interpolator<S, T>::toString().c_str();
+                ss << ", mOptionFlags=" << static_cast<int32_t>(mOptionFlags);
+                ss << ", mDurationMs=" << mDurationMs;
+                ss << ", " << Interpolator<S, T>::toString().c_str();
             }
+            ss << "}";
             return ss.str();
         }
 
     private:
-        Type mType;
-        int32_t mId;
-        OptionFlag mOptionFlags;
-        double mDurationMs;
+        Type mType;              // type of configuration
+        int32_t mId;             // A valid id is >= 0.
+        OptionFlag mOptionFlags; // option flags for the configuration.
+        double mDurationMs;      // duration, must be > 0; default is 1000 ms.
     }; // Configuration
 
-    // must match with VolumeShaper.java in frameworks/base
-    // TODO document per VolumeShaper.java flags.
+    /* VolumeShaper::Operation expresses an operation to perform on the
+     * configuration (either explicitly specified or an id).
+     *
+     * This parallels the Java implementation and the enums must match.
+     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
+     * details on the Java implementation.
+     */
     class Operation : public RefBase {
     public:
+        // Must match with VolumeShaper.java.
         enum Flag : int32_t {
             FLAG_NONE      = 0,
-            FLAG_REVERSE   = (1 << 0),
+            FLAG_REVERSE   = (1 << 0), // the absence of this indicates "play"
             FLAG_TERMINATE = (1 << 1),
             FLAG_JOIN      = (1 << 2),
             FLAG_DELAY     = (1 << 3),
@@ -332,13 +389,29 @@
         }
 
         void setXOffset(S xOffset) {
-            mXOffset = xOffset;
+            mXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */);
         }
 
         Flag getFlags() const {
             return mFlags;
         }
 
+        /* xOffset is the position on the volume curve and may go backwards
+         * if you are in reverse mode. This must be in the range from
+         * [MIN_CURVE_TIME, MAX_CURVE_TIME].
+         *
+         * normalizedTime always increases as time or framecount increases.
+         * normalizedTime is nominally from MIN_CURVE_TIME to MAX_CURVE_TIME when
+         * running through the curve, but could be outside this range afterwards.
+         * If you are reversing, this means the position on the curve, or xOffset,
+         * is computed as MAX_CURVE_TIME - normalizedTime, clamped to
+         * [MIN_CURVE_TIME, MAX_CURVE_TIME].
+         */
+        void setNormalizedTime(S normalizedTime) {
+            setXOffset((mFlags & FLAG_REVERSE) != 0
+                    ? MAX_CURVE_TIME - normalizedTime : normalizedTime);
+        }
+
         status_t setFlags(Flag flags) {
             if ((flags & ~FLAG_ALL) != 0) {
                 ALOGE("flags has invalid bits: %#x", flags);
@@ -365,19 +438,26 @@
 
         std::string toString() const {
             std::stringstream ss;
-            ss << "mFlags: " << mFlags << std::endl;
-            ss << "mReplaceId: " << mReplaceId << std::endl;
-            ss << "mXOffset: " << mXOffset << std::endl;
+            ss << "VolumeShaper::Operation{mFlags=" << static_cast<int32_t>(mFlags) ;
+            ss << ", mReplaceId=" << mReplaceId;
+            ss << ", mXOffset=" << mXOffset;
+            ss << "}";
             return ss.str();
         }
 
     private:
-        Flag mFlags;
-        int32_t mReplaceId;
-        S mXOffset;
+        Flag mFlags;        // operation to do
+        int32_t mReplaceId; // if >= 0 the id to remove in a replace operation.
+        S mXOffset;         // position in the curve to set if a valid number (not nan)
     }; // Operation
 
-    // must match with VolumeShaper.java in frameworks/base
+    /* VolumeShaper.State is returned when requesting the last
+     * state of the VolumeShaper.
+     *
+     * This parallels the Java implementation.
+     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
+     * details on the Java implementation.
+     */
     class State : public RefBase {
     public:
         State(T volume, S xOffset)
@@ -386,7 +466,7 @@
         }
 
         State()
-            : State(-1.f, -1.f) { }
+            : State(NAN, NAN) { }
 
         T getVolume() const {
             return mVolume;
@@ -417,16 +497,18 @@
 
         std::string toString() const {
             std::stringstream ss;
-            ss << "mVolume: " << mVolume << std::endl;
-            ss << "mXOffset: " << mXOffset << std::endl;
+            ss << "VolumeShaper::State{mVolume=" << mVolume;
+            ss << ", mXOffset=" << mXOffset;
+            ss << "}";
             return ss.str();
         }
 
     private:
-        T mVolume;
-        S mXOffset;
+        T mVolume;   // linear volume in the range MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME
+        S mXOffset;  // position on curve expressed from MIN_CURVE_TIME to MAX_CURVE_TIME
     }; // State
 
+    // Internal helper class to do an affine transform for time and amplitude scaling.
     template <typename R>
     class Translate {
     public:
@@ -457,8 +539,9 @@
 
         std::string toString() const {
             std::stringstream ss;
-            ss << "mOffset: " << mOffset << std::endl;
-            ss << "mScale: " << mScale << std::endl;
+            ss << "VolumeShaper::Translate{mOffset=" << mOffset;
+            ss << ", mScale=" << mScale;
+            ss << "}";
             return ss.str();
         }
 
@@ -482,9 +565,14 @@
         return convertTimespecToUs(tv);
     }
 
-    // TODO: Since we pass configuration and operation as shared pointers
-    // there is a potential risk that the caller may modify these after
-    // delivery.  Currently, we don't require copies made here.
+    /* Native implementation of VolumeShaper.  This is NOT mirrored
+     * on the Java side, so we don't need to mimic Java side layout
+     * and data; furthermore, this isn't refcounted as a "RefBase" object.
+     *
+     * Since we pass configuration and operation as shared pointers (like
+     * Java) there is a potential risk that the caller may modify
+     * these after delivery.
+     */
     VolumeShaper(
             const sp<VolumeShaper::Configuration> &configuration,
             const sp<VolumeShaper::Operation> &operation)
@@ -492,53 +580,58 @@
         , mOperation(operation)         // ditto
         , mStartFrame(-1)
         , mLastVolume(T(1))
-        , mLastXOffset(0.f)
-        , mDelayXOffset(std::numeric_limits<S>::quiet_NaN()) {
+        , mLastXOffset(MIN_CURVE_TIME)
+        , mDelayXOffset(MIN_CURVE_TIME) {
         if (configuration.get() != nullptr
                 && (getFlags() & VolumeShaper::Operation::FLAG_DELAY) == 0) {
             mLastVolume = configuration->first().second;
         }
     }
 
-    void updatePosition(int64_t startFrame, double sampleRate) {
-        double scale = (mConfiguration->last().first - mConfiguration->first().first)
-                        / (mConfiguration->getDurationMs() * 0.001 * sampleRate);
-        const double minScale = 1. / INT64_MAX;
-        scale = std::max(scale, minScale);
-        const S xOffset = std::isnan(mDelayXOffset) ? mConfiguration->first().first : mDelayXOffset;
-        VS_LOG("update position: scale %lf  frameCount:%lld, sampleRate:%lf, xOffset:%f",
-                scale, (long long) startFrame, sampleRate, xOffset);
-
-        mXTranslate.setOffset(startFrame - xOffset / scale);
-        mXTranslate.setScale(scale);
-        VS_LOG("translate: %s", mXTranslate.toString().c_str());
-    }
-
     // We allow a null operation here, though VolumeHandler always provides one.
     VolumeShaper::Operation::Flag getFlags() const {
         return mOperation == nullptr
-                ? VolumeShaper::Operation::FLAG_NONE :mOperation->getFlags();
+                ? VolumeShaper::Operation::FLAG_NONE : mOperation->getFlags();
     }
 
+    /* Returns the last volume and xoffset reported to the AudioFlinger.
+     * If the VolumeShaper has not been started, compute what the volume
+     * should be based on the initial offset specified.
+     */
     sp<VolumeShaper::State> getState() const {
-        return new VolumeShaper::State(mLastVolume, mLastXOffset);
+        if (!isStarted()) {
+            const T volume = computeVolumeFromXOffset(mDelayXOffset);
+            VS_LOG("delayed VolumeShaper, using cached offset:%f for volume:%f",
+                    mDelayXOffset, volume);
+            return new VolumeShaper::State(volume, mDelayXOffset);
+        } else {
+            return new VolumeShaper::State(mLastVolume, mLastXOffset);
+        }
+    }
+
+    S getDelayXOffset() const {
+        return mDelayXOffset;
     }
 
     void setDelayXOffset(S xOffset) {
-        mDelayXOffset = xOffset;
+        mDelayXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */);
     }
 
     bool isStarted() const {
         return mStartFrame >= 0;
     }
 
+    /* getVolume() updates the last volume/xoffset state so it is not
+     * const, even though logically it may be viewed as const.
+     */
     std::pair<T /* volume */, bool /* active */> getVolume(
             int64_t trackFrameCount, double trackSampleRate) {
         if ((getFlags() & VolumeShaper::Operation::FLAG_DELAY) != 0) {
-            VS_LOG("delayed VolumeShaper, ignoring");
-            mLastVolume = T(1);
-            mLastXOffset = 0.;
-            return std::make_pair(T(1), false);
+            // We haven't had PLAY called yet, so just return the value
+            // as if PLAY were called just now.
+            VS_LOG("delayed VolumeShaper, using cached offset %f", mDelayXOffset);
+            const T volume = computeVolumeFromXOffset(mDelayXOffset);
+            return std::make_pair(volume, false);
         }
         const bool clockTime = (mConfiguration->getOptionFlags()
                 & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0;
@@ -546,84 +639,110 @@
         const double sampleRate = clockTime ? 1000000 : trackSampleRate;
 
         if (mStartFrame < 0) {
-            updatePosition(frameCount, sampleRate);
+            updatePosition(frameCount, sampleRate, mDelayXOffset);
             mStartFrame = frameCount;
         }
         VS_LOG("frameCount: %lld", (long long)frameCount);
-        S x = mXTranslate((T)frameCount);
-        VS_LOG("translation: %f", x);
+        const S x = mXTranslate((T)frameCount);
+        VS_LOG("translation to normalized time: %f", x);
 
-        // handle reversal of position
-        if (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) {
-            x = 1.f - x;
-            VS_LOG("reversing to %f", x);
-            if (x < mConfiguration->first().first) {
-                mLastXOffset = 1.f;
-                const T volume = mConfiguration->adjustVolume(
-                        mConfiguration->first().second);  // persist last value
-                VS_LOG("persisting volume %f", volume);
-                mLastVolume = volume;
-                return std::make_pair(volume, false);
-            }
-            if (x > mConfiguration->last().first) {
-                mLastXOffset = 0.f;
-                mLastVolume = 1.f;
-                return std::make_pair(T(1), true); // too early
-            }
-        } else {
-            if (x < mConfiguration->first().first) {
-                mLastXOffset = 0.f;
-                mLastVolume = 1.f;
-                return std::make_pair(T(1), true); // too early
-            }
-            if (x > mConfiguration->last().first) {
-                mLastXOffset = 1.f;
-                const T volume = mConfiguration->adjustVolume(
-                        mConfiguration->last().second);  // persist last value
-                VS_LOG("persisting volume %f", volume);
-                mLastVolume = volume;
-                return std::make_pair(volume, false);
-            }
-        }
-        mLastXOffset = x;
-        // x contains the location on the volume curve to use.
-        const T unscaledVolume = mConfiguration->findY(x);
-        const T volume = mConfiguration->adjustVolume(unscaledVolume); // handle log scale
-        VS_LOG("volume: %f  unscaled: %f", volume, unscaledVolume);
-        mLastVolume = volume;
-        return std::make_pair(volume, true);
+        std::tuple<T /* volume */, S /* position */, bool /* active */> vt =
+                computeStateFromNormalizedTime(x);
+
+        mLastVolume = std::get<0>(vt);
+        mLastXOffset = std::get<1>(vt);
+        const bool active = std::get<2>(vt);
+        VS_LOG("rescaled time:%f  volume:%f  xOffset:%f  active:%s",
+                x, mLastVolume, mLastXOffset, active ? "true" : "false");
+        return std::make_pair(mLastVolume, active);
     }
 
     std::string toString() const {
         std::stringstream ss;
-        ss << "StartFrame: " << mStartFrame << std::endl;
-        ss << mXTranslate.toString().c_str();
-        if (mConfiguration.get() == nullptr) {
-            ss << "VolumeShaper::Configuration: nullptr" << std::endl;
-        } else {
-            ss << "VolumeShaper::Configuration:" << std::endl;
-            ss << mConfiguration->toString().c_str();
-        }
-        if (mOperation.get() == nullptr) {
-            ss << "VolumeShaper::Operation: nullptr" << std::endl;
-        } else {
-            ss << "VolumeShaper::Operation:" << std::endl;
-            ss << mOperation->toString().c_str();
-        }
+        ss << "VolumeShaper{mStartFrame=" << mStartFrame;
+        ss << ", mXTranslate=" << mXTranslate.toString().c_str();
+        ss << ", mConfiguration=" <<
+                (mConfiguration.get() == nullptr
+                        ? "nullptr" : mConfiguration->toString().c_str());
+        ss << ", mOperation=" <<
+                (mOperation.get() == nullptr
+                        ? "nullptr" :  mOperation->toString().c_str());
+        ss << "}";
         return ss.str();
     }
 
-    Translate<S> mXTranslate; // x axis translation from frames (in usec for clock time)
+    Translate<S> mXTranslate; // translation from frames (usec for clock time) to normalized time.
     sp<VolumeShaper::Configuration> mConfiguration;
     sp<VolumeShaper::Operation> mOperation;
+
+private:
     int64_t mStartFrame; // starting frame, non-negative when started (in usec for clock time)
     T mLastVolume;       // last computed interpolated volume (y-axis)
     S mLastXOffset;      // last computed interpolated xOffset/time (x-axis)
-    S mDelayXOffset;     // delay xOffset on first volumeshaper start.
+    S mDelayXOffset;     // xOffset to use for first invocation of VolumeShaper.
+
+    // Called internally to adjust mXTranslate for first time start.
+    void updatePosition(int64_t startFrame, double sampleRate, S xOffset) {
+        double scale = (mConfiguration->last().first - mConfiguration->first().first)
+                        / (mConfiguration->getDurationMs() * 0.001 * sampleRate);
+        const double minScale = 1. / static_cast<double>(INT64_MAX);
+        scale = std::max(scale, minScale);
+        VS_LOG("update position: scale %lf  frameCount:%lld, sampleRate:%lf, xOffset:%f",
+                scale, (long long) startFrame, sampleRate, xOffset);
+
+        S normalizedTime = (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ?
+                MAX_CURVE_TIME - xOffset : xOffset;
+        mXTranslate.setOffset(static_cast<float>(static_cast<double>(startFrame)
+                                                 - static_cast<double>(normalizedTime) / scale));
+        mXTranslate.setScale(static_cast<float>(scale));
+        VS_LOG("translate: %s", mXTranslate.toString().c_str());
+    }
+
+    T computeVolumeFromXOffset(S xOffset) const {
+        const T unscaledVolume = mConfiguration->findY(xOffset);
+        const T volume = mConfiguration->adjustVolume(unscaledVolume); // handle log scale
+        VS_LOG("computeVolumeFromXOffset %f -> %f -> %f", xOffset, unscaledVolume, volume);
+        return volume;
+    }
+
+    std::tuple<T /* volume */, S /* position */, bool /* active */>
+    computeStateFromNormalizedTime(S x) const {
+        bool active = true;
+        // handle reversal of position
+        if (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) {
+            x = MAX_CURVE_TIME - x;
+            VS_LOG("reversing to %f", x);
+            if (x < MIN_CURVE_TIME) {
+                x = MIN_CURVE_TIME;
+                active = false; // at the end
+            } else if (x > MAX_CURVE_TIME) {
+                x = MAX_CURVE_TIME; //early
+            }
+        } else {
+            if (x < MIN_CURVE_TIME) {
+                x = MIN_CURVE_TIME; // early
+            } else if (x > MAX_CURVE_TIME) {
+                x = MAX_CURVE_TIME;
+                active = false; // at end
+            }
+        }
+        const S xOffset = x;
+        const T volume = computeVolumeFromXOffset(xOffset);
+        return std::make_tuple(volume, xOffset, active);
+    }
 }; // VolumeShaper
 
-// VolumeHandler combines the volume factors of multiple VolumeShapers and handles
-// multiple thread access by synchronizing all public methods.
+/* VolumeHandler combines the volume factors of multiple VolumeShapers associated
+ * with a player.  It is thread safe by synchronizing all public methods.
+ *
+ * This is a native-only implementation.
+ *
+ * The server side VolumeHandler is used to maintain a list of volume handlers,
+ * keep state, and obtain volume.
+ *
+ * The client side VolumeHandler is used to maintain a list of volume handlers,
+ * keep some partial state, and restore if the server dies.
+ */
 class VolumeHandler : public RefBase {
 public:
     using S = float;
@@ -637,13 +756,15 @@
     explicit VolumeHandler(uint32_t sampleRate)
         : mSampleRate((double)sampleRate)
         , mLastFrame(0)
-        , mVolumeShaperIdCounter(VolumeShaper::kSystemIdMax)
+        , mVolumeShaperIdCounter(VolumeShaper::kSystemVolumeShapersMax)
         , mLastVolume(1.f, false) {
     }
 
     VolumeShaper::Status applyVolumeShaper(
             const sp<VolumeShaper::Configuration> &configuration,
-            const sp<VolumeShaper::Operation> &operation) {
+            const sp<VolumeShaper::Operation> &operation_in) {
+        // make a local copy of operation, as we modify it.
+        sp<VolumeShaper::Operation> operation(new VolumeShaper::Operation(operation_in));
         VS_LOG("applyVolumeShaper:configuration: %s", configuration->toString().c_str());
         VS_LOG("applyVolumeShaper:operation: %s", operation->toString().c_str());
         AutoMutex _l(mLock);
@@ -666,14 +787,16 @@
         case VolumeShaper::Configuration::TYPE_SCALE: {
             const int replaceId = operation->getReplaceId();
             if (replaceId >= 0) {
+                VS_LOG("replacing %d", replaceId);
                 auto replaceIt = findId_l(replaceId);
                 if (replaceIt == mVolumeShapers.end()) {
                     ALOGW("cannot find replace id: %d", replaceId);
                 } else {
-                    if ((replaceIt->getFlags() & VolumeShaper::Operation::FLAG_JOIN) != 0) {
+                    if ((operation->getFlags() & VolumeShaper::Operation::FLAG_JOIN) != 0) {
                         // For join, we scale the start volume of the current configuration
                         // to match the last-used volume of the replacing VolumeShaper.
                         auto state = replaceIt->getState();
+                        ALOGD("join: state:%s", state->toString().c_str());
                         if (state->getXOffset() >= 0) { // valid
                             const T volume = state->getVolume();
                             ALOGD("join: scaling start volume to %f", volume);
@@ -695,8 +818,22 @@
                 ALOGW("duplicate id, removing old %d", id);
                 (void)mVolumeShapers.erase(oldIt);
             }
-            // create new VolumeShaper
-            mVolumeShapers.emplace_back(configuration, operation);
+
+            /* Check if too many application VolumeShapers (with id >= kSystemVolumeShapersMax).
+             * We check on the server side to ensure synchronization and robustness.
+             *
+             * This shouldn't fail on a replace command unless the replaced id is
+             * already invalid (which *should* be checked in the Java layer).
+             */
+            if (id >= VolumeShaper::kSystemVolumeShapersMax
+                    && numberOfUserVolumeShapers_l() >= VolumeShaper::kUserVolumeShapersMax) {
+                ALOGW("Too many app VolumeShapers, cannot add to VolumeHandler");
+                return VolumeShaper::Status(INVALID_OPERATION);
+            }
+
+            // create new VolumeShaper with default behavior.
+            mVolumeShapers.emplace_back(configuration, new VolumeShaper::Operation());
+            VS_LOG("after adding, number of volumeShapers:%zu", mVolumeShapers.size());
         }
         // fall through to handle the operation
         HANDLE_TYPE_ID:
@@ -707,7 +844,7 @@
                 VS_LOG("couldn't find id: %d", id);
                 return VolumeShaper::Status(INVALID_OPERATION);
             }
-            if ((it->getFlags() & VolumeShaper::Operation::FLAG_TERMINATE) != 0) {
+            if ((operation->getFlags() & VolumeShaper::Operation::FLAG_TERMINATE) != 0) {
                 VS_LOG("terminate id: %d", id);
                 mVolumeShapers.erase(it);
                 break;
@@ -716,29 +853,37 @@
                     & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0;
             if ((it->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) !=
                     (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE)) {
-                const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
-                const S x = it->mXTranslate((T)frameCount);
-                VS_LOG("reverse translation: %f", x);
-                // reflect position
-                S target = 1.f - x;
-                if (target < it->mConfiguration->first().first) {
-                    VS_LOG("clamp to start - begin immediately");
-                    target = 0.;
+                if (it->isStarted()) {
+                    const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
+                    const S x = it->mXTranslate((T)frameCount);
+                    VS_LOG("reverse normalizedTime: %f", x);
+                    // reflect position
+                    S target = MAX_CURVE_TIME - x;
+                    if (target < MIN_CURVE_TIME) {
+                        VS_LOG("clamp to start - begin immediately");
+                        target = MIN_CURVE_TIME;
+                    }
+                    VS_LOG("reverse normalizedTime target: %f", target);
+                    it->mXTranslate.setOffset(it->mXTranslate.getOffset()
+                            + (x - target) / it->mXTranslate.getScale());
                 }
-                VS_LOG("target reverse: %f", target);
-                it->mXTranslate.setOffset(it->mXTranslate.getOffset()
-                        + (x - target) / it->mXTranslate.getScale());
+                // if not started, the delay offset doesn't change.
             }
             const S xOffset = operation->getXOffset();
             if (!std::isnan(xOffset)) {
-                const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
-                const S x = it->mXTranslate((T)frameCount);
-                VS_LOG("xOffset translation: %f", x);
-                const S target = xOffset; // offset
-                VS_LOG("xOffset target x offset: %f", target);
-                it->mXTranslate.setOffset(it->mXTranslate.getOffset()
-                        + (x - target) / it->mXTranslate.getScale());
-                it->setDelayXOffset(xOffset);
+                if (it->isStarted()) {
+                    const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
+                    const S x = it->mXTranslate((T)frameCount);
+                    VS_LOG("normalizedTime translation: %f", x);
+                    const S target =
+                            (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ?
+                                    MAX_CURVE_TIME - xOffset : xOffset;
+                    VS_LOG("normalizedTime target x offset: %f", target);
+                    it->mXTranslate.setOffset(it->mXTranslate.getOffset()
+                            + (x - target) / it->mXTranslate.getScale());
+                } else {
+                    it->setDelayXOffset(xOffset);
+                }
             }
             it->mOperation = operation; // replace the operation
         } break;
@@ -756,28 +901,31 @@
         return it->getState();
     }
 
-    // getVolume() is not const, as it updates internal state.
-    // Once called, any VolumeShapers not already started begin running.
+    /* getVolume() is not const, as it updates internal state.
+     * Once called, any VolumeShapers not already started begin running.
+     */
     std::pair<T /* volume */, bool /* active */> getVolume(int64_t trackFrameCount) {
         AutoMutex _l(mLock);
         mLastFrame = trackFrameCount;
         T volume(1);
         size_t activeCount = 0;
         for (auto it = mVolumeShapers.begin(); it != mVolumeShapers.end();) {
-            std::pair<T, bool> shaperVolume =
+            const std::pair<T, bool> shaperVolume =
                     it->getVolume(trackFrameCount, mSampleRate);
             volume *= shaperVolume.first;
             activeCount += shaperVolume.second;
             ++it;
         }
         mLastVolume = std::make_pair(volume, activeCount != 0);
+        VS_LOG("getVolume: <%f, %s>", mLastVolume.first, mLastVolume.second ? "true" : "false");
         return mLastVolume;
     }
 
-    // Used by a client side VolumeHandler to ensure all the VolumeShapers
-    // indicate that they have been started.  Upon a change in audioserver
-    // output sink, this information is used for restoration of the server side
-    // VolumeHandler.
+    /* Used by a client side VolumeHandler to ensure all the VolumeShapers
+     * indicate that they have been started.  Upon a change in audioserver
+     * output sink, this information is used for restoration of the server side
+     * VolumeHandler.
+     */
     void setStarted() {
         (void)getVolume(mLastFrame);  // getVolume() will start the individual VolumeShapers.
     }
@@ -790,11 +938,19 @@
     std::string toString() const {
         AutoMutex _l(mLock);
         std::stringstream ss;
-        ss << "mSampleRate: " << mSampleRate << std::endl;
-        ss << "mLastFrame: " << mLastFrame << std::endl;
+        ss << "VolumeHandler{mSampleRate=" << mSampleRate;
+        ss << ", mLastFrame=" << mLastFrame;
+        ss << ", mVolumeShapers={";
+        bool first = true;
         for (const auto &shaper : mVolumeShapers) {
+            if (first) {
+                first = false;
+            } else {
+                ss << ", ";
+            }
             ss << shaper.toString().c_str();
         }
+        ss << "}}";
         return ss.str();
     }
 
@@ -814,8 +970,9 @@
         // keep mVolumeShaperIdCounter as is.
     }
 
-    // Sets the configuration id if necessary - This is based on the counter
-    // internal to the VolumeHandler.
+    /* Sets the configuration id if necessary - This is based on the counter
+     * internal to the VolumeHandler.
+     */
     void setIdIfNecessary(const sp<VolumeShaper::Configuration> &configuration) {
         if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
             const int id = configuration->getId();
@@ -824,7 +981,7 @@
                 AutoMutex _l(mLock);
                 while (true) {
                     if (mVolumeShaperIdCounter == INT32_MAX) {
-                        mVolumeShaperIdCounter = VolumeShaper::kSystemIdMax;
+                        mVolumeShaperIdCounter = VolumeShaper::kSystemVolumeShapersMax;
                     } else {
                         ++mVolumeShaperIdCounter;
                     }
@@ -850,6 +1007,14 @@
         return it;
     }
 
+    size_t numberOfUserVolumeShapers_l() const {
+        size_t count = 0;
+        for (const auto &shaper : mVolumeShapers) {
+            count += (shaper.mConfiguration->getId() >= VolumeShaper::kSystemVolumeShapersMax);
+        }
+        return count;
+    }
+
     mutable Mutex mLock;
     double mSampleRate; // in samples (frames) per second
     int64_t mLastFrame; // logging purpose only, 0 on start
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
index b482e93..7c34252 100644
--- a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
+++ b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
@@ -24,7 +24,7 @@
 #include <time.h>
 #include <aaudio/AAudio.h>
 
-#define NUM_SECONDS           10
+#define NUM_SECONDS           5
 #define NANOS_PER_MICROSECOND ((int64_t)1000)
 #define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
 #define NANOS_PER_SECOND      (NANOS_PER_MILLISECOND * 1000)
@@ -33,13 +33,13 @@
 #define SHARING_MODE  AAUDIO_SHARING_MODE_SHARED
 
 /**
- * Simple wrapper for AAudio that opens a default stream and then calls
- * a callback function to fill the output buffers.
+ * Simple wrapper for AAudio that opens an input stream and then calls
+ * a callback function to process the input data.
  */
-class SimpleAAudioPlayer {
+class SimpleAAudioRecorder {
 public:
-    SimpleAAudioPlayer() {}
-    ~SimpleAAudioPlayer() {
+    SimpleAAudioRecorder() {}
+    ~SimpleAAudioRecorder() {
         close();
     };
 
@@ -71,6 +71,15 @@
         }
         return AAudioStream_getSamplesPerFrame(mStream);;
     }
+    /**
+     * Only call this after open() has been called.
+     */
+    int64_t getFramesRead() {
+        if (mStream == nullptr) {
+            return AAUDIO_ERROR_INVALID_STATE;
+        }
+        return AAudioStream_getFramesRead(mStream);;
+    }
 
     /**
      * Open a stream
@@ -85,7 +94,7 @@
         AAudioStreamBuilder_setDirection(mBuilder, AAUDIO_DIRECTION_INPUT);
         AAudioStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
         AAudioStreamBuilder_setDataCallback(mBuilder, proc, userContext);
-        AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_I16);
+        AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_FLOAT);
 
         // Open an AAudioStream using the Builder.
         result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
@@ -121,11 +130,10 @@
     }
 
     // Write zero data to fill up the buffer and prevent underruns.
-    // Assume format is PCM_I16. TODO use floats.
     aaudio_result_t prime() {
         int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(mStream);
         const int numFrames = 32; // arbitrary
-        int16_t zeros[numFrames * samplesPerFrame];
+        float zeros[numFrames * samplesPerFrame];
         memset(zeros, 0, sizeof(zeros));
         aaudio_result_t result = numFrames;
         while (result == numFrames) {
@@ -151,8 +159,16 @@
             fprintf(stderr, "ERROR - AAudioStream_requestStop() returned %d %s\n",
                     result, AAudio_convertResultToText(result));
         }
-        int32_t xRunCount = AAudioStream_getXRunCount(mStream);
-        printf("AAudioStream_getXRunCount %d\n", xRunCount);
+        return result;
+    }
+
+    // Pause the stream. AAudio will stop calling your callback function.
+    aaudio_result_t pause() {
+        aaudio_result_t result = AAudioStream_requestPause(mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_requestPause() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+        }
         return result;
     }
 
@@ -227,7 +243,7 @@
 int main(int argc, char **argv)
 {
     (void)argc; // unused
-    SimpleAAudioPlayer player;
+    SimpleAAudioRecorder recorder;
     PeakTrackerData_t myData = {0.0};
     aaudio_result_t result;
     const int displayRateHz = 20; // arbitrary
@@ -238,37 +254,60 @@
     setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
     printf("%s - Display audio input using an AAudio callback\n", argv[0]);
 
-    player.setSharingMode(SHARING_MODE);
+    recorder.setSharingMode(SHARING_MODE);
 
-    result = player.open(MyDataCallbackProc, &myData);
+    result = recorder.open(MyDataCallbackProc, &myData);
     if (result != AAUDIO_OK) {
-        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
+        fprintf(stderr, "ERROR -  recorder.open() returned %d\n", result);
         goto error;
     }
-    printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
-    printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+    printf("recorder.getFramesPerSecond() = %d\n", recorder.getFramesPerSecond());
+    printf("recorder.getSamplesPerFrame() = %d\n", recorder.getSamplesPerFrame());
 
-    result = player.start();
+    result = recorder.start();
     if (result != AAUDIO_OK) {
-        fprintf(stderr, "ERROR -  player.start() returned %d\n", result);
+        fprintf(stderr, "ERROR -  recorder.start() returned %d\n", result);
         goto error;
     }
 
-    printf("Sleep for %d seconds while audio plays in a callback thread.\n", NUM_SECONDS);
-   for (int i = 0; i < loopsNeeded; i++)
+    printf("Sleep for %d seconds while audio record in a callback thread.\n", NUM_SECONDS);
+    for (int i = 0; i < loopsNeeded; i++)
     {
         const struct timespec request = { .tv_sec = 0,
                 .tv_nsec = NANOS_PER_SECOND / displayRateHz };
         (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+        printf("%08d: ", (int)recorder.getFramesRead());
+        displayPeakLevel(myData.peakLevel);
+    }
+    printf("Woke up. Stop for a moment.\n");
+
+    result = recorder.stop();
+    if (result != AAUDIO_OK) {
+        goto error;
+    }
+    sleep(1);
+    result = recorder.start();
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  recorder.start() returned %d\n", result);
+        goto error;
+    }
+
+    printf("Sleep for %d seconds while audio records in a callback thread.\n", NUM_SECONDS);
+    for (int i = 0; i < loopsNeeded; i++)
+    {
+        const struct timespec request = { .tv_sec = 0,
+                .tv_nsec = NANOS_PER_SECOND / displayRateHz };
+        (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+        printf("%08d: ", (int)recorder.getFramesRead());
         displayPeakLevel(myData.peakLevel);
     }
     printf("Woke up now.\n");
 
-    result = player.stop();
+    result = recorder.stop();
     if (result != AAUDIO_OK) {
         goto error;
     }
-    result = player.close();
+    result = recorder.close();
     if (result != AAUDIO_OK) {
         goto error;
     }
@@ -276,7 +315,7 @@
     printf("SUCCESS\n");
     return EXIT_SUCCESS;
 error:
-    player.close();
+    recorder.close();
     printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
     return EXIT_FAILURE;
 }
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 8498950..4e36e84 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -209,14 +209,13 @@
 /**
  * Request a sample rate in Hertz.
  *
- * The stream may be opened with a different sample rate.
- * So the application should query for the actual rate after the stream is opened.
- *
- * Technically, this should be called the "frame rate" or "frames per second",
- * because it refers to the number of complete frames transferred per second.
- * But it is traditionally called "sample rate". So we use that term.
- *
  * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ * An optimal value will then be chosen when the stream is opened.
+ * After opening a stream with an unspecified value, the application must
+ * query for the actual value, which may vary by device.
+ *
+ * If an exact value is specified then an opened stream will use that value.
+ * If a stream cannot be opened with the specified value then the open will fail.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
  * @param sampleRate frames per second. Common rates include 44100 and 48000 Hz.
@@ -227,12 +226,13 @@
 /**
  * Request a number of channels for the stream.
  *
- * The stream may be opened with a different value.
- * So the application should query for the actual value after the stream is opened.
- *
  * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ * An optimal value will then be chosen when the stream is opened.
+ * After opening a stream with an unspecified value, the application must
+ * query for the actual value, which may vary by device.
  *
- * Note, this quantity is sometimes referred to as "samples per frame".
+ * If an exact value is specified then an opened stream will use that value.
+ * If a stream cannot be opened with the specified value then the open will fail.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
  * @param channelCount Number of channels desired.
@@ -252,12 +252,15 @@
  * Request a sample data format, for example AAUDIO_FORMAT_PCM_I16.
  *
  * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ * An optimal value will then be chosen when the stream is opened.
+ * After opening a stream with an unspecified value, the application must
+ * query for the actual value, which may vary by device.
  *
- * The stream may be opened with a different value.
- * So the application should query for the actual value after the stream is opened.
+ * If an exact value is specified then an opened stream will use that value.
+ * If a stream cannot be opened with the specified value then the open will fail.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
- * @param format Most common formats are AAUDIO_FORMAT_PCM_FLOAT and AAUDIO_FORMAT_PCM_I16.
+ * @param format common formats are AAUDIO_FORMAT_PCM_FLOAT and AAUDIO_FORMAT_PCM_I16.
  */
 AAUDIO_API void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder,
                                                    aaudio_audio_format_t format);
@@ -507,6 +510,9 @@
  * Use AAudioStream_Start() to resume playback after a pause.
  * After this call the state will be in AAUDIO_STREAM_STATE_PAUSING or AAUDIO_STREAM_STATE_PAUSED.
  *
+ * This will return AAUDIO_ERROR_UNIMPLEMENTED for input streams.
+ * For input streams use AAudioStream_requestStop().
+ *
  * @param stream reference provided by AAudioStreamBuilder_openStream()
  * @return AAUDIO_OK or a negative error.
  */
@@ -519,6 +525,8 @@
  * Frame counters are not reset by a flush. They may be advanced.
  * After this call the state will be in AAUDIO_STREAM_STATE_FLUSHING or AAUDIO_STREAM_STATE_FLUSHED.
  *
+ * This will return AAUDIO_ERROR_UNIMPLEMENTED for input streams.
+ *
  * @param stream reference provided by AAudioStreamBuilder_openStream()
  * @return AAUDIO_OK or a negative error.
  */
@@ -704,6 +712,9 @@
  *
  * An underrun or overrun can cause an audible "pop" or "glitch".
  *
+ * Note that some INPUT devices may not support this function.
+ * In that case a 0 will always be returned.
+ *
  * @param stream reference provided by AAudioStreamBuilder_openStream()
  * @return the underrun or overrun count
  */
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.h b/media/libaaudio/src/binding/AAudioBinderClient.h
index f7f2808..ca2da29 100644
--- a/media/libaaudio/src/binding/AAudioBinderClient.h
+++ b/media/libaaudio/src/binding/AAudioBinderClient.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_AAUDIO_BINDER_CLIENT_H
-#define AAUDIO_AAUDIO_BINDER_CLIENT_H
+#ifndef ANDROID_AAUDIO_AAUDIO_BINDER_CLIENT_H
+#define ANDROID_AAUDIO_AAUDIO_BINDER_CLIENT_H
 
 #include <aaudio/AAudio.h>
 #include "AAudioServiceDefinitions.h"
@@ -91,4 +91,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_AAUDIO_BINDER_CLIENT_H
+#endif //ANDROID_AAUDIO_AAUDIO_BINDER_CLIENT_H
diff --git a/media/libaaudio/src/binding/AAudioServiceInterface.h b/media/libaaudio/src/binding/AAudioServiceInterface.h
index b565499..824e5bc 100644
--- a/media/libaaudio/src/binding/AAudioServiceInterface.h
+++ b/media/libaaudio/src/binding/AAudioServiceInterface.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
-#define AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
+#ifndef ANDROID_AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
+#define ANDROID_AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
 
 #include "binding/AAudioServiceDefinitions.h"
 #include "binding/AAudioStreamRequest.h"
@@ -87,4 +87,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
+#endif //ANDROID_AAUDIO_BINDING_AAUDIO_SERVICE_INTERFACE_H
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index d75aa32..b4377fb 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_AAUDIO_SERVICE_MESSAGE_H
-#define AAUDIO_AAUDIO_SERVICE_MESSAGE_H
+#ifndef ANDROID_AAUDIO_AAUDIO_SERVICE_MESSAGE_H
+#define ANDROID_AAUDIO_AAUDIO_SERVICE_MESSAGE_H
 
 #include <stdint.h>
 
@@ -64,4 +64,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_AAUDIO_SERVICE_MESSAGE_H
+#endif //ANDROID_AAUDIO_AAUDIO_SERVICE_MESSAGE_H
diff --git a/media/libaaudio/src/binding/AAudioStreamConfiguration.h b/media/libaaudio/src/binding/AAudioStreamConfiguration.h
index 78c4983..b1e4a7d 100644
--- a/media/libaaudio/src/binding/AAudioStreamConfiguration.h
+++ b/media/libaaudio/src/binding/AAudioStreamConfiguration.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_AAUDIO_STREAM_CONFIGURATION_H
-#define BINDING_AAUDIO_STREAM_CONFIGURATION_H
+#ifndef ANDROID_BINDING_AAUDIO_STREAM_CONFIGURATION_H
+#define ANDROID_BINDING_AAUDIO_STREAM_CONFIGURATION_H
 
 #include <stdint.h>
 
@@ -101,4 +101,4 @@
 
 } /* namespace aaudio */
 
-#endif //BINDING_AAUDIO_STREAM_CONFIGURATION_H
+#endif //ANDROID_BINDING_AAUDIO_STREAM_CONFIGURATION_H
diff --git a/media/libaaudio/src/binding/AAudioStreamRequest.h b/media/libaaudio/src/binding/AAudioStreamRequest.h
index d4bfbe1..77138da 100644
--- a/media/libaaudio/src/binding/AAudioStreamRequest.h
+++ b/media/libaaudio/src/binding/AAudioStreamRequest.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_AAUDIO_STREAM_REQUEST_H
-#define BINDING_AAUDIO_STREAM_REQUEST_H
+#ifndef ANDROID_BINDING_AAUDIO_STREAM_REQUEST_H
+#define ANDROID_BINDING_AAUDIO_STREAM_REQUEST_H
 
 #include <stdint.h>
 
@@ -95,4 +95,4 @@
 
 } /* namespace aaudio */
 
-#endif //BINDING_AAUDIO_STREAM_REQUEST_H
+#endif //ANDROID_BINDING_AAUDIO_STREAM_REQUEST_H
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h
index 4a1cb72..993075c7 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.h
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_AUDIOENDPOINTPARCELABLE_H
-#define BINDING_AUDIOENDPOINTPARCELABLE_H
+#ifndef ANDROID_BINDING_AUDIO_ENDPOINT_PARCELABLE_H
+#define ANDROID_BINDING_AUDIO_ENDPOINT_PARCELABLE_H
 
 #include <stdint.h>
 
@@ -75,4 +75,4 @@
 
 } /* namespace aaudio */
 
-#endif //BINDING_AUDIOENDPOINTPARCELABLE_H
+#endif //ANDROID_BINDING_AUDIO_ENDPOINT_PARCELABLE_H
diff --git a/media/libaaudio/src/binding/IAAudioService.h b/media/libaaudio/src/binding/IAAudioService.h
index 2cee651..44a5e12 100644
--- a/media/libaaudio/src/binding/IAAudioService.h
+++ b/media/libaaudio/src/binding/IAAudioService.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_IAAUDIOSERVICE_H
-#define BINDING_IAAUDIOSERVICE_H
+#ifndef ANDROID_AAUDIO_IAAUDIO_SERVICE_H
+#define ANDROID_AAUDIO_IAAUDIO_SERVICE_H
 
 #include <stdint.h>
 #include <utils/RefBase.h>
@@ -103,4 +103,4 @@
 
 } /* namespace android */
 
-#endif //BINDING_IAAUDIOSERVICE_H
+#endif //ANDROID_AAUDIO_IAAUDIO_SERVICE_H
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp
index 05451f9..6b74b21 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.cpp
+++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -79,29 +79,49 @@
  * The read and write must be symmetric.
  */
 status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
-    parcel->writeInt32(mCapacityInFrames);
+    status_t status = parcel->writeInt32(mCapacityInFrames);
+    if (status != NO_ERROR) goto error;
     if (mCapacityInFrames > 0) {
-        parcel->writeInt32(mBytesPerFrame);
-        parcel->writeInt32(mFramesPerBurst);
-        parcel->writeInt32(mFlags);
-        mReadCounterParcelable.writeToParcel(parcel);
-        mWriteCounterParcelable.writeToParcel(parcel);
-        mDataParcelable.writeToParcel(parcel);
+        status = parcel->writeInt32(mBytesPerFrame);
+        if (status != NO_ERROR) goto error;
+        status = parcel->writeInt32(mFramesPerBurst);
+        if (status != NO_ERROR) goto error;
+        status = parcel->writeInt32(mFlags);
+        if (status != NO_ERROR) goto error;
+        status = mReadCounterParcelable.writeToParcel(parcel);
+        if (status != NO_ERROR) goto error;
+        status = mWriteCounterParcelable.writeToParcel(parcel);
+        if (status != NO_ERROR) goto error;
+        status = mDataParcelable.writeToParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return NO_ERROR;
+error:
+    ALOGE("RingBufferParcelable::writeToParcel() error = %d", status);
+    return status;
 }
 
 status_t RingBufferParcelable::readFromParcel(const Parcel* parcel) {
-    parcel->readInt32(&mCapacityInFrames);
+    status_t status = parcel->readInt32(&mCapacityInFrames);
+    if (status != NO_ERROR) goto error;
     if (mCapacityInFrames > 0) {
-        parcel->readInt32(&mBytesPerFrame);
-        parcel->readInt32(&mFramesPerBurst);
-        parcel->readInt32((int32_t *)&mFlags);
-        mReadCounterParcelable.readFromParcel(parcel);
-        mWriteCounterParcelable.readFromParcel(parcel);
-        mDataParcelable.readFromParcel(parcel);
+        status = parcel->readInt32(&mBytesPerFrame);
+        if (status != NO_ERROR) goto error;
+        status = parcel->readInt32(&mFramesPerBurst);
+        if (status != NO_ERROR) goto error;
+        status = parcel->readInt32((int32_t *)&mFlags);
+        if (status != NO_ERROR) goto error;
+        status = mReadCounterParcelable.readFromParcel(parcel);
+        if (status != NO_ERROR) goto error;
+        status = mWriteCounterParcelable.readFromParcel(parcel);
+        if (status != NO_ERROR) goto error;
+        status = mDataParcelable.readFromParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return NO_ERROR;
+error:
+    ALOGE("RingBufferParcelable::readFromParcel() error = %d", status);
+    return status;
 }
 
 aaudio_result_t RingBufferParcelable::resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor) {
@@ -141,7 +161,7 @@
         ALOGE("RingBufferParcelable invalid mBytesPerFrame = %d", mBytesPerFrame);
         return AAUDIO_ERROR_INTERNAL;
     }
-    if (mFramesPerBurst < 0 || mFramesPerBurst >= 1024) {
+    if (mFramesPerBurst < 0 || mFramesPerBurst >= 16 * 1024) {
         ALOGE("RingBufferParcelable invalid mFramesPerBurst = %d", mFramesPerBurst);
         return AAUDIO_ERROR_INTERNAL;
     }
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h
index 5fc5d00..bd562f2 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.h
+++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_RINGBUFFER_PARCELABLE_H
-#define BINDING_RINGBUFFER_PARCELABLE_H
+#ifndef ANDROID_AAUDIO_RINGBUFFER_PARCELABLE_H
+#define ANDROID_AAUDIO_RINGBUFFER_PARCELABLE_H
 
 #include <stdint.h>
 
@@ -82,4 +82,4 @@
 
 } /* namespace aaudio */
 
-#endif //BINDING_RINGBUFFER_PARCELABLE_H
+#endif //ANDROID_AAUDIO_RINGBUFFER_PARCELABLE_H
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
index 7e77ca0..1094d9e 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -50,7 +50,8 @@
     if (status != NO_ERROR) return status;
     if (mSizeInBytes > 0) {
         status = parcel->writeDupFileDescriptor(mFd);
-        ALOGE_IF(status != NO_ERROR, "SharedMemoryParcelable writeDupFileDescriptor failed : %d", status);
+        ALOGE_IF(status != NO_ERROR, "SharedMemoryParcelable writeDupFileDescriptor failed : %d",
+                 status);
     }
     return status;
 }
@@ -61,11 +62,13 @@
         return status;
     }
     if (mSizeInBytes > 0) {
-        int originalFD = parcel->readFileDescriptor();
-        mFd = fcntl(originalFD, F_DUPFD_CLOEXEC, 0);
+        mOriginalFd = parcel->readFileDescriptor();
+        ALOGV("SharedMemoryParcelable::readFromParcel() LEAK? mOriginalFd = %d\n", mOriginalFd);
+        mFd = fcntl(mOriginalFd, F_DUPFD_CLOEXEC, 0);
+        ALOGV("SharedMemoryParcelable::readFromParcel() LEAK? mFd = %d\n", mFd);
         if (mFd == -1) {
             status = -errno;
-            ALOGE("SharedMemoryParcelable readFileDescriptor fcntl() failed : %d", status);
+            ALOGE("SharedMemoryParcelable readFromParcel fcntl() failed : %d", status);
         }
     }
     return status;
@@ -81,9 +84,15 @@
         mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
     }
     if (mFd != -1) {
+        ALOGV("SharedMemoryParcelable::close() LEAK? mFd = %d\n", mFd);
         ::close(mFd);
         mFd = -1;
     }
+    if (mOriginalFd != -1) {
+        ALOGV("SharedMemoryParcelable::close() LEAK? mOriginalFd = %d\n", mOriginalFd);
+        ::close(mOriginalFd);
+        mOriginalFd = -1;
+    }
     return AAUDIO_OK;
 }
 
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h
index c4feb48..4b94b46 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.h
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_SHAREDMEMORYPARCELABLE_H
-#define BINDING_SHAREDMEMORYPARCELABLE_H
+#ifndef ANDROID_AAUDIO_SHARED_MEMORY_PARCELABLE_H
+#define ANDROID_AAUDIO_SHARED_MEMORY_PARCELABLE_H
 
 #include <stdint.h>
 
@@ -67,11 +67,12 @@
 
 #define MMAP_UNRESOLVED_ADDRESS    reinterpret_cast<uint8_t*>(MAP_FAILED)
 
-    int mFd = -1;
-    int32_t mSizeInBytes = 0;
+    int      mFd = -1;
+    int      mOriginalFd = -1;
+    int32_t  mSizeInBytes = 0;
     uint8_t *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
 };
 
 } /* namespace aaudio */
 
-#endif //BINDING_SHAREDMEMORYPARCELABLE_H
+#endif //ANDROID_AAUDIO_SHARED_MEMORY_PARCELABLE_H
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h
index e9f5785..f6babfd 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.h
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BINDING_SHAREDREGIONPARCELABLE_H
-#define BINDING_SHAREDREGIONPARCELABLE_H
+#ifndef ANDROID_AAUDIO_SHARED_REGION_PARCELABLE_H
+#define ANDROID_AAUDIO_SHARED_REGION_PARCELABLE_H
 
 #include <stdint.h>
 
@@ -59,4 +59,4 @@
 
 } /* namespace aaudio */
 
-#endif //BINDING_SHAREDREGIONPARCELABLE_H
+#endif //ANDROID_AAUDIO_SHARED_REGION_PARCELABLE_H
diff --git a/media/libaaudio/src/client/AudioEndpoint.cpp b/media/libaaudio/src/client/AudioEndpoint.cpp
index 6f87df6..027d66d 100644
--- a/media/libaaudio/src/client/AudioEndpoint.cpp
+++ b/media/libaaudio/src/client/AudioEndpoint.cpp
@@ -28,6 +28,9 @@
 using namespace android;
 using namespace aaudio;
 
+#define RIDICULOUSLY_LARGE_BUFFER_CAPACITY   (256 * 1024)
+#define RIDICULOUSLY_LARGE_FRAME_SIZE        4096
+
 AudioEndpoint::AudioEndpoint()
     : mOutputFreeRunning(false)
     , mDataReadCounter(0)
@@ -45,16 +48,22 @@
         ALOGE("AudioEndpoint_validateQueueDescriptor() NULL descriptor");
         return AAUDIO_ERROR_NULL;
     }
-    if (descriptor->capacityInFrames <= 0) {
+
+    if (descriptor->capacityInFrames < 1
+        || descriptor->capacityInFrames > RIDICULOUSLY_LARGE_BUFFER_CAPACITY) {
         ALOGE("AudioEndpoint_validateQueueDescriptor() bad capacityInFrames = %d",
               descriptor->capacityInFrames);
         return AAUDIO_ERROR_OUT_OF_RANGE;
     }
-    if (descriptor->bytesPerFrame <= 1) {
+
+    // Reject extreme values to catch bugs and prevent numeric overflows.
+    if (descriptor->bytesPerFrame < 1
+        || descriptor->bytesPerFrame > RIDICULOUSLY_LARGE_FRAME_SIZE) {
         ALOGE("AudioEndpoint_validateQueueDescriptor() bad bytesPerFrame = %d",
               descriptor->bytesPerFrame);
         return AAUDIO_ERROR_OUT_OF_RANGE;
     }
+
     if (descriptor->dataAddress == nullptr) {
         ALOGE("AudioEndpoint_validateQueueDescriptor() NULL dataAddress");
         return AAUDIO_ERROR_NULL;
@@ -82,6 +91,7 @@
         *descriptor->readCounterAddress = counter;
         ALOGV("AudioEndpoint_validateQueueDescriptor() wrote readCounterAddress successfully");
     }
+
     if (descriptor->writeCounterAddress) {
         fifo_counter_t counter = *descriptor->writeCounterAddress;
         ALOGV("AudioEndpoint_validateQueueDescriptor() *writeCounterAddress = %d, now write",
@@ -89,6 +99,7 @@
         *descriptor->writeCounterAddress = counter;
         ALOGV("AudioEndpoint_validateQueueDescriptor() wrote writeCounterAddress successfully");
     }
+
     return AAUDIO_OK;
 }
 
@@ -104,7 +115,6 @@
 
 aaudio_result_t AudioEndpoint::configure(const EndpointDescriptor *pEndpointDescriptor)
 {
-    // TODO maybe remove after debugging
     aaudio_result_t result = AudioEndpoint_validateDescriptor(pEndpointDescriptor);
     if (result != AAUDIO_OK) {
         ALOGE("AudioEndpoint_validateQueueDescriptor returned %d %s",
@@ -112,10 +122,19 @@
         return result;
     }
 
+    // ============================ up message queue =============================
     const RingBufferDescriptor *descriptor = &pEndpointDescriptor->upMessageQueueDescriptor;
-    assert(descriptor->bytesPerFrame == sizeof(AAudioServiceMessage));
-    assert(descriptor->readCounterAddress != nullptr);
-    assert(descriptor->writeCounterAddress != nullptr);
+    if(descriptor->bytesPerFrame != sizeof(AAudioServiceMessage)) {
+        ALOGE("AudioEndpoint::configure() bytesPerFrame != sizeof(AAudioServiceMessage) = %d",
+              descriptor->bytesPerFrame);
+        return AAUDIO_ERROR_INTERNAL;
+    }
+
+    if(descriptor->readCounterAddress == nullptr || descriptor->writeCounterAddress == nullptr) {
+        ALOGE("AudioEndpoint_validateQueueDescriptor() NULL counter address");
+        return AAUDIO_ERROR_NULL;
+    }
+
     mUpCommandQueue = new FifoBuffer(
             descriptor->bytesPerFrame,
             descriptor->capacityInFrames,
@@ -123,25 +142,9 @@
             descriptor->writeCounterAddress,
             descriptor->dataAddress
     );
-    /* TODO mDownCommandQueue
-    if (descriptor->capacityInFrames > 0) {
-        descriptor = &pEndpointDescriptor->downMessageQueueDescriptor;
-        mDownCommandQueue = new FifoBuffer(
-                descriptor->capacityInFrames,
-                descriptor->bytesPerFrame,
-                descriptor->readCounterAddress,
-                descriptor->writeCounterAddress,
-                descriptor->dataAddress
-        );
-    }
-     */
+
+    // ============================ down data queue =============================
     descriptor = &pEndpointDescriptor->downDataQueueDescriptor;
-    assert(descriptor->capacityInFrames > 0);
-    assert(descriptor->bytesPerFrame > 1);
-    assert(descriptor->bytesPerFrame < 4 * 16); // FIXME just for initial debugging
-    assert(descriptor->framesPerBurst > 0);
-    assert(descriptor->framesPerBurst < 8 * 1024); // FIXME just for initial debugging
-    assert(descriptor->dataAddress != nullptr);
     ALOGV("AudioEndpoint::configure() data framesPerBurst = %d", descriptor->framesPerBurst);
     ALOGV("AudioEndpoint::configure() data readCounterAddress = %p", descriptor->readCounterAddress);
     mOutputFreeRunning = descriptor->readCounterAddress == nullptr;
@@ -172,8 +175,6 @@
 
 aaudio_result_t AudioEndpoint::writeDataNow(const void *buffer, int32_t numFrames)
 {
-    // TODO Make it easier for the AAudioStreamInternal to scale floats and write shorts
-    // TODO Similar to block adapter write through technique. Add a DataConverter.
     return mDownDataQueue->write(buffer, numFrames);
 }
 
diff --git a/media/libaaudio/src/client/AudioEndpoint.h b/media/libaaudio/src/client/AudioEndpoint.h
index a24a705..46a3fc5 100644
--- a/media/libaaudio/src/client/AudioEndpoint.h
+++ b/media/libaaudio/src/client/AudioEndpoint.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_AUDIO_ENDPOINT_H
-#define AAUDIO_AUDIO_ENDPOINT_H
+#ifndef ANDROID_AAUDIO_AUDIO_ENDPOINT_H
+#define ANDROID_AAUDIO_AUDIO_ENDPOINT_H
 
 #include <aaudio/AAudio.h>
 
@@ -93,4 +93,4 @@
 
 } // namespace aaudio
 
-#endif //AAUDIO_AUDIO_ENDPOINT_H
+#endif //ANDROID_AAUDIO_AUDIO_ENDPOINT_H
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 5214db8..eee860e 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -244,7 +244,7 @@
 
     ALOGD("AudioStreamInternal(): callbackLoop() exiting, result = %d, isPlaying() = %d",
           result, (int) isPlaying());
-    return NULL; // TODO review
+    return NULL;
 }
 
 static void *aaudio_callback_thread_proc(void *context)
@@ -402,7 +402,7 @@
 aaudio_result_t AudioStreamInternal::getTimestamp(clockid_t clockId,
                            int64_t *framePosition,
                            int64_t *timeNanoseconds) {
-    // TODO implement using real HAL
+    // TODO Generate in server and pass to client. Return latest.
     int64_t time = AudioClock::getNanoseconds();
     *framePosition = mClockModel.convertTimeToPosition(time);
     *timeNanoseconds = time + (10 * AAUDIO_NANOS_PER_MILLISECOND); // Fake hardware delay
@@ -574,12 +574,9 @@
 // Write as much data as we can without blocking.
 aaudio_result_t AudioStreamInternal::writeNow(const void *buffer, int32_t numFrames,
                                          int64_t currentNanoTime, int64_t *wakeTimePtr) {
-
-    {
-        aaudio_result_t result = processCommands();
-        if (result != AAUDIO_OK) {
-            return result;
-        }
+    aaudio_result_t result = processCommands();
+    if (result != AAUDIO_OK) {
+        return result;
     }
 
     if (mAudioEndpoint.isOutputFreeRunning()) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index a43f6c5..ee602c1 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_AUDIOSTREAMINTERNAL_H
-#define AAUDIO_AUDIOSTREAMINTERNAL_H
+#ifndef ANDROID_AAUDIO_AUDIO_STREAM_INTERNAL_H
+#define ANDROID_AAUDIO_AUDIO_STREAM_INTERNAL_H
 
 #include <stdint.h>
 #include <aaudio/AAudio.h>
@@ -49,15 +49,11 @@
 
     aaudio_result_t requestStop() override;
 
-    // TODO use aaudio_clockid_t all the way down to AudioClock
     aaudio_result_t getTimestamp(clockid_t clockId,
                                        int64_t *framePosition,
                                        int64_t *timeNanoseconds) override;
 
-
-
     virtual aaudio_result_t updateStateWhileWaiting() override;
-
     // =========== End ABSTRACT methods ===========================
 
     aaudio_result_t open(const AudioStreamBuilder &builder) override;
@@ -170,4 +166,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_AUDIOSTREAMINTERNAL_H
+#endif //ANDROID_AAUDIO_AUDIO_STREAM_INTERNAL_H
diff --git a/media/libaaudio/src/client/IsochronousClockModel.h b/media/libaaudio/src/client/IsochronousClockModel.h
index 205c341..0314f55 100644
--- a/media/libaaudio/src/client/IsochronousClockModel.h
+++ b/media/libaaudio/src/client/IsochronousClockModel.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
-#define AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
+#ifndef ANDROID_AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
+#define ANDROID_AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
 
 #include <stdint.h>
 
@@ -106,4 +106,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
+#endif //ANDROID_AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index c3cf27f..d1698bf 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -66,6 +66,18 @@
         ALOGE("AudioStream::open(): samplesPerFrame out of range = %d", mSamplesPerFrame);
         return AAUDIO_ERROR_OUT_OF_RANGE;
     }
+
+    switch(mFormat) {
+        case AAUDIO_FORMAT_UNSPECIFIED:
+        case AAUDIO_FORMAT_PCM_I16:
+        case AAUDIO_FORMAT_PCM_FLOAT:
+            break; // valid
+        default:
+            ALOGE("AudioStream::open(): audioFormat not valid = %d", mFormat);
+            return AAUDIO_ERROR_INVALID_FORMAT;
+            // break;
+    }
+
     if (mSampleRate < 0 || mSampleRate > 1000000) {
         ALOGE("AudioStream::open(): mSampleRate out of range = %d", mSampleRate);
         return AAUDIO_ERROR_INVALID_RATE;
@@ -101,7 +113,6 @@
         return result;
     }
 
-    // TODO replace this when similar functionality added to AudioTrack.cpp
     int64_t durationNanos = 20 * AAUDIO_NANOS_PER_MILLISECOND; // arbitrary
     aaudio_stream_state_t state = getState();
     while (state == currentState && timeoutNanoseconds > 0) {
@@ -145,6 +156,8 @@
     return audioStream->wrapUserThread();
 }
 
+// This is not exposed in the API.
+// But it is still used internally to implement callbacks for MMAP mode.
 aaudio_result_t AudioStream::createThread(int64_t periodNanoseconds,
                                      aaudio_audio_thread_proc_t threadProc,
                                      void* threadArg)
@@ -161,8 +174,7 @@
     setPeriodNanoseconds(periodNanoseconds);
     int err = pthread_create(&mThread, nullptr, AudioStream_internalThreadProc, this);
     if (err != 0) {
-        // TODO convert errno to aaudio_result_t
-        return AAUDIO_ERROR_INTERNAL;
+        return AAudioConvert_androidToAAudioResult(-errno);
     } else {
         mHasThread = true;
         return AAUDIO_OK;
@@ -182,7 +194,6 @@
     int err = pthread_join(mThread, returnArg);
 #endif
     mHasThread = false;
-    // TODO convert errno to aaudio_result_t
-    return err ? AAUDIO_ERROR_INTERNAL : mThreadRegistrationResult;
+    return err ? AAudioConvert_androidToAAudioResult(-errno) : mThreadRegistrationResult;
 }
 
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp
index 9bc2ef2..f313b58 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.cpp
+++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -91,7 +91,8 @@
     AudioStream *audioStream = nullptr;
     *streamPtr = nullptr;
 
-    bool tryMMap = (sharingMode == AAUDIO_SHARING_MODE_SHARED) && MMAP_SHARED_ENABLED;
+    bool tryMMap = ((sharingMode == AAUDIO_SHARING_MODE_SHARED) && MMAP_SHARED_ENABLED) ||
+            ((sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE) && MMAP_EXCLUSIVE_ENABLED);
     aaudio_result_t result = builder_createStream(getDirection(), sharingMode,
                                                   tryMMap, &audioStream);
     if (result == AAUDIO_OK) {
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index 59d754a..f89234a 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -108,3 +108,24 @@
             break;
     }
 }
+
+aaudio_result_t AudioStreamLegacy::getBestTimestamp(clockid_t clockId,
+                                                   int64_t *framePosition,
+                                                   int64_t *timeNanoseconds,
+                                                   ExtendedTimestamp *extendedTimestamp) {
+    int timebase;
+    switch (clockId) {
+        case CLOCK_BOOTTIME:
+            timebase = ExtendedTimestamp::TIMEBASE_BOOTTIME;
+            break;
+        case CLOCK_MONOTONIC:
+            timebase = ExtendedTimestamp::TIMEBASE_MONOTONIC;
+            break;
+        default:
+            ALOGE("getTimestamp() - Unrecognized clock type %d", (int) clockId);
+            return AAUDIO_ERROR_UNEXPECTED_VALUE;
+            break;
+    }
+    status_t status = extendedTimestamp->getBestTimestamp(framePosition, timeNanoseconds, timebase);
+    return AAudioConvert_androidToAAudioResult(status);
+}
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
index d5a3ede..38f1a56 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.h
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -17,6 +17,7 @@
 #ifndef LEGACY_AUDIO_STREAM_LEGACY_H
 #define LEGACY_AUDIO_STREAM_LEGACY_H
 
+#include <media/AudioTimestamp.h>
 
 #include <aaudio/AAudio.h>
 
@@ -73,6 +74,12 @@
     virtual int64_t incrementClientFrameCounter(int32_t frames)  = 0;
 
 protected:
+
+    aaudio_result_t getBestTimestamp(clockid_t clockId,
+                                     int64_t *framePosition,
+                                     int64_t *timeNanoseconds,
+                                     android::ExtendedTimestamp *extendedTimestamp);
+
     FixedBlockAdapter         *mBlockAdapter = nullptr;
     aaudio_wrapping_frames_t   mPositionWhenStarting = 0;
     int32_t                    mCallbackBufferSize = 0;
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index f0a6ceb..eb6bfd5 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -177,10 +177,13 @@
 
 aaudio_result_t AudioStreamRecord::requestPause()
 {
+    // This does not make sense for an input stream.
+    // There is no real difference between pause() and stop().
     return AAUDIO_ERROR_UNIMPLEMENTED;
 }
 
 aaudio_result_t AudioStreamRecord::requestFlush() {
+    // This does not make sense for an input stream.
     return AAUDIO_ERROR_UNIMPLEMENTED;
 }
 
@@ -259,7 +262,7 @@
 
 int32_t AudioStreamRecord::getXRunCount() const
 {
-    return AAUDIO_ERROR_UNIMPLEMENTED; // TODO implement when AudioRecord supports it
+    return 0; // TODO implement when AudioRecord supports it
 }
 
 int32_t AudioStreamRecord::getFramesPerBurst() const
@@ -275,20 +278,5 @@
     if (status != NO_ERROR) {
         return AAudioConvert_androidToAAudioResult(status);
     }
-    // TODO Merge common code into AudioStreamLegacy after rebasing.
-    int timebase;
-    switch(clockId) {
-        case CLOCK_BOOTTIME:
-            timebase = ExtendedTimestamp::TIMEBASE_BOOTTIME;
-            break;
-        case CLOCK_MONOTONIC:
-            timebase = ExtendedTimestamp::TIMEBASE_MONOTONIC;
-            break;
-        default:
-            ALOGE("getTimestamp() - Unrecognized clock type %d", (int) clockId);
-            return AAUDIO_ERROR_UNEXPECTED_VALUE;
-            break;
-    }
-    status = extendedTimestamp.getBestTimestamp(framePosition, timeNanoseconds, timebase);
-    return AAudioConvert_androidToAAudioResult(status);
+    return getBestTimestamp(clockId, framePosition, timeNanoseconds, &extendedTimestamp);
 }
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index b4b1c04..a7c7673 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -61,7 +61,7 @@
     ALOGD("AudioStreamTrack::open = %p", this);
 
     // Try to create an AudioTrack
-    // TODO Support UNSPECIFIED in AudioTrack. For now, use stereo if unspecified.
+    // Use stereo if unspecified.
     int32_t samplesPerFrame = (getSamplesPerFrame() == AAUDIO_UNSPECIFIED)
                               ? 2 : getSamplesPerFrame();
     audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(samplesPerFrame);
@@ -163,9 +163,8 @@
 
 aaudio_result_t AudioStreamTrack::close()
 {
-    // TODO maybe add close() or release() to AudioTrack API then call it from here
     if (getState() != AAUDIO_STREAM_STATE_CLOSED) {
-        mAudioTrack.clear(); // TODO is this right?
+        mAudioTrack.clear();
         setState(AAUDIO_STREAM_STATE_CLOSED);
     }
     mFixedBlockReader.close();
@@ -290,7 +289,7 @@
             if (err != OK) {
                 return AAudioConvert_androidToAAudioResult(err);
             } else if (position == 0) {
-                // Advance frames read to match written.
+                // TODO Advance frames read to match written.
                 setState(AAUDIO_STREAM_STATE_FLUSHED);
             }
         }
@@ -389,20 +388,5 @@
     if (status != NO_ERROR) {
         return AAudioConvert_androidToAAudioResult(status);
     }
-    // TODO Merge common code into AudioStreamLegacy after rebasing.
-    int timebase;
-    switch (clockId) {
-        case CLOCK_BOOTTIME:
-            timebase = ExtendedTimestamp::TIMEBASE_BOOTTIME;
-            break;
-        case CLOCK_MONOTONIC:
-            timebase = ExtendedTimestamp::TIMEBASE_MONOTONIC;
-            break;
-        default:
-            ALOGE("getTimestamp() - Unrecognized clock type %d", (int) clockId);
-            return AAUDIO_ERROR_UNEXPECTED_VALUE;
-            break;
-    }
-    status = extendedTimestamp.getBestTimestamp(framePosition, timeNanoseconds, timebase);
-    return AAudioConvert_androidToAAudioResult(status);
+    return getBestTimestamp(clockId, framePosition, timeNanoseconds, &extendedTimestamp);
 }
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 5fa228a..be2bd10 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -48,7 +48,7 @@
 }
 
 
-// TODO call clamp16_from_float function in primitives.h
+// TODO expose and call clamp16_from_float function in primitives.h
 static inline int16_t clamp16_from_float(float f) {
     /* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of the
      * floating point significand. The normal shift is 3<<22, but the -15 offset
@@ -209,18 +209,32 @@
         status = DEAD_OBJECT;
         break;
     case AAUDIO_ERROR_INVALID_STATE:
+    case AAUDIO_ERROR_UNEXPECTED_STATE:
         status = INVALID_OPERATION;
         break;
-    case AAUDIO_ERROR_UNEXPECTED_VALUE: // TODO redundant?
+    case AAUDIO_ERROR_UNEXPECTED_VALUE:
     case AAUDIO_ERROR_INVALID_RATE:
     case AAUDIO_ERROR_INVALID_FORMAT:
     case AAUDIO_ERROR_ILLEGAL_ARGUMENT:
+    case AAUDIO_ERROR_OUT_OF_RANGE:
         status = BAD_VALUE;
         break;
     case AAUDIO_ERROR_WOULD_BLOCK:
         status = WOULD_BLOCK;
         break;
-    // TODO add more result codes
+    case AAUDIO_ERROR_NULL:
+        status = UNEXPECTED_NULL;
+        break;
+    // TODO translate these result codes
+    case AAUDIO_ERROR_INCOMPATIBLE:
+    case AAUDIO_ERROR_INTERNAL:
+    case AAUDIO_ERROR_INVALID_QUERY:
+    case AAUDIO_ERROR_UNIMPLEMENTED:
+    case AAUDIO_ERROR_UNAVAILABLE:
+    case AAUDIO_ERROR_NO_FREE_HANDLES:
+    case AAUDIO_ERROR_NO_MEMORY:
+    case AAUDIO_ERROR_TIMEOUT:
+    case AAUDIO_ERROR_NO_SERVICE:
     default:
         status = UNKNOWN_ERROR;
         break;
@@ -244,13 +258,15 @@
     case INVALID_OPERATION:
         result = AAUDIO_ERROR_INVALID_STATE;
         break;
-    case BAD_VALUE:
-        result = AAUDIO_ERROR_UNEXPECTED_VALUE;
-        break;
+        case UNEXPECTED_NULL:
+            result = AAUDIO_ERROR_NULL;
+            break;
+        case BAD_VALUE:
+            result = AAUDIO_ERROR_UNEXPECTED_VALUE;
+            break;
     case WOULD_BLOCK:
         result = AAUDIO_ERROR_WOULD_BLOCK;
         break;
-    // TODO add more status codes
     default:
         result = AAUDIO_ERROR_INTERNAL;
         break;
diff --git a/media/libaaudio/src/utility/FixedBlockAdapter.cpp b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
index f4666af..63495f0 100644
--- a/media/libaaudio/src/utility/FixedBlockAdapter.cpp
+++ b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
@@ -25,7 +25,7 @@
 int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
 {
     mSize = bytesPerFixedBlock;
-    mStorage = new uint8_t[bytesPerFixedBlock]; // TODO use std::nothrow
+    mStorage = new uint8_t[bytesPerFixedBlock];
     mPosition = 0;
     return 0;
 }
diff --git a/media/libaaudio/tests/Android.mk b/media/libaaudio/tests/Android.mk
index 01360b1..fba81f2 100644
--- a/media/libaaudio/tests/Android.mk
+++ b/media/libaaudio/tests/Android.mk
@@ -47,3 +47,15 @@
 LOCAL_STATIC_LIBRARIES := libaaudio
 LOCAL_MODULE := test_linear_ramp
 include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/libaaudio/include \
+    frameworks/av/media/libaaudio/src
+LOCAL_SRC_FILES:= test_open_params.cpp
+LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
+                          libcutils liblog libmedia libutils
+LOCAL_STATIC_LIBRARIES := libaaudio
+LOCAL_MODULE := test_open_params
+include $(BUILD_NATIVE_TEST)
diff --git a/media/libaaudio/tests/test_open_params.cpp b/media/libaaudio/tests/test_open_params.cpp
new file mode 100644
index 0000000..5125653
--- /dev/null
+++ b/media/libaaudio/tests/test_open_params.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Play sine waves using AAudio.
+
+#include <stdio.h>
+//#include <stdlib.h>
+//#include <math.h>
+
+#include <android-base/macros.h>
+#include <aaudio/AAudio.h>
+
+#include <gtest/gtest.h>
+
+static const char *getSharingModeText(aaudio_sharing_mode_t mode) {
+    const char *modeText = "unknown";
+    switch (mode) {
+    case AAUDIO_SHARING_MODE_EXCLUSIVE:
+        modeText = "EXCLUSIVE";
+        break;
+    case AAUDIO_SHARING_MODE_SHARED:
+        modeText = "SHARED";
+        break;
+    default:
+        break;
+    }
+    return modeText;
+}
+
+// Callback function that fills the audio output buffer.
+aaudio_data_callback_result_t MyDataCallbackProc(
+        AAudioStream *stream,
+        void *userData,
+        void *audioData,
+        int32_t numFrames
+) {
+    (void) stream;
+    (void) userData;
+    (void) audioData;
+    (void) numFrames;
+    return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static void testOpenOptions(aaudio_direction_t direction,
+                            int32_t channelCount,
+                            int32_t sampleRate,
+                            aaudio_audio_format_t format) {
+
+    aaudio_result_t result = AAUDIO_OK;
+
+    int32_t bufferCapacity;
+    int32_t framesPerBurst = 0;
+
+    int32_t actualChannelCount = 0;
+    int32_t actualSampleRate = 0;
+    aaudio_audio_format_t actualDataFormat = AAUDIO_FORMAT_UNSPECIFIED;
+    aaudio_sharing_mode_t actualSharingMode = AAUDIO_SHARING_MODE_SHARED;
+    aaudio_direction_t actualDirection;
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    AAudioStream *aaudioStream = nullptr;
+
+    printf("TestOpen: dir = %d, chans = %3d, rate = %6d format = %d\n",
+           direction, channelCount, sampleRate, format);
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+    AAudioStreamBuilder_setSampleRate(aaudioBuilder, sampleRate);
+    AAudioStreamBuilder_setChannelCount(aaudioBuilder, channelCount);
+    AAudioStreamBuilder_setFormat(aaudioBuilder, format);
+    AAudioStreamBuilder_setDataCallback(aaudioBuilder, MyDataCallbackProc, nullptr);
+
+    //AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_NONE);
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+    //AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_POWER_SAVING);
+
+    // Create an AAudioStream using the Builder.
+    result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
+    if (result != AAUDIO_OK) {
+        printf("Stream not opened! That may be OK.\n");
+        goto finish;
+    }
+
+    // Check to see what kind of stream we actually got.
+    actualSampleRate = AAudioStream_getSampleRate(aaudioStream);
+    actualChannelCount = AAudioStream_getChannelCount(aaudioStream);
+    actualDataFormat = AAudioStream_getFormat(aaudioStream);
+    actualDirection = AAudioStream_getDirection(aaudioStream);
+
+    printf("          dir = %d, chans = %3d, rate = %6d format = %d\n",
+           direction, actualChannelCount, actualSampleRate, actualDataFormat);
+
+    // If we ask for something specific then we should get that.
+    if (channelCount != AAUDIO_UNSPECIFIED) {
+        EXPECT_EQ(channelCount, actualChannelCount);
+    }
+    if (sampleRate != AAUDIO_UNSPECIFIED) {
+        EXPECT_EQ(sampleRate, actualSampleRate);
+    }
+    if (format != AAUDIO_FORMAT_UNSPECIFIED) {
+        EXPECT_EQ(format, actualDataFormat);
+    }
+    EXPECT_EQ(direction, actualDirection);
+
+    // This is the number of frames that are read in one chunk by a DMA controller
+    // or a DSP or a mixer.
+    framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream);
+    bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream);
+    printf("          bufferCapacity = %d, remainder = %d\n",
+           bufferCapacity, bufferCapacity % framesPerBurst);
+
+finish:
+    AAudioStream_close(aaudioStream);
+    AAudioStreamBuilder_delete(aaudioBuilder);
+    printf("          result = %d = %s\n", result, AAudio_convertResultToText(result));
+}
+
+//void foo() { // for tricking the Android Studio formatter
+TEST(test_open_params, aaudio_open_all) {
+    aaudio_direction_t directions[] = {AAUDIO_DIRECTION_OUTPUT, AAUDIO_DIRECTION_INPUT};
+    aaudio_audio_format_t formats[] = {AAUDIO_FORMAT_UNSPECIFIED, AAUDIO_FORMAT_PCM_I16, AAUDIO_FORMAT_PCM_FLOAT};
+    int32_t rates[] = {AAUDIO_UNSPECIFIED, 22050, 32000, 44100, 48000, 88200, 96000, 37913, 59132};
+
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+    for (uint dirIndex = 0;dirIndex < arraysize(directions); dirIndex++) {
+        aaudio_direction_t direction = directions[dirIndex];
+        for (int32_t channelCount = 0; channelCount <= 8; channelCount++) {
+            testOpenOptions(direction, channelCount,
+                            AAUDIO_UNSPECIFIED, AAUDIO_FORMAT_UNSPECIFIED);
+        }
+        for (uint i = 0; i < arraysize(rates); i++) {
+            testOpenOptions(direction, AAUDIO_UNSPECIFIED, rates[i], AAUDIO_FORMAT_UNSPECIFIED);
+        }
+        for (uint i = 0; i < arraysize(formats); i++) {
+            testOpenOptions(direction, AAUDIO_UNSPECIFIED, AAUDIO_UNSPECIFIED, formats[i]);
+        }
+    }
+}
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 4baf253..38d90bc 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -2268,7 +2268,7 @@
             // For now, we simply advance to the end of the VolumeShaper effect
             // if it has been started.
             if (shaper.isStarted()) {
-                operationToEnd->setXOffset(1.f);
+                operationToEnd->setNormalizedTime(1.f);
             }
             return mAudioTrack->applyVolumeShaper(shaper.mConfiguration, operationToEnd);
         });
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index 16eb225..a4c8d53 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -874,6 +874,10 @@
      */
             bool hasStarted(); // not const
 
+            bool isPlaying() {
+                AutoMutex lock(mLock);
+                return mState == STATE_ACTIVE || mState == STATE_STOPPING;
+            }
 protected:
     /* copying audio tracks is not allowed */
                         AudioTrack(const AudioTrack& other);
diff --git a/media/libaudiohal/StreamHalHidl.cpp b/media/libaudiohal/StreamHalHidl.cpp
index 5b06b73..42785d5 100644
--- a/media/libaudiohal/StreamHalHidl.cpp
+++ b/media/libaudiohal/StreamHalHidl.cpp
@@ -158,7 +158,7 @@
                 if (retval == Result::OK) {
                     const native_handle *handle = hidlInfo.sharedMemory.handle();
                     if (handle->numFds > 0) {
-                        info->shared_memory_fd = dup(handle->data[0]);
+                        info->shared_memory_fd = handle->data[0];
                         info->buffer_size_frames = hidlInfo.bufferSizeFrames;
                         info->burst_size_frames = hidlInfo.burstSizeFrames;
                         // info->shared_memory_address is not needed in HIDL context
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index f050e7f..dc501b2 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -41,6 +41,7 @@
     friend class MediaAnalyticsService;
     friend class IMediaAnalyticsService;
     friend class MediaMetricsJNI;
+    friend class MetricsSummarizer;
 
     public:
 
@@ -231,7 +232,6 @@
         size_t mPropCount;
         size_t mPropSize;
         Prop *mProps;
-
 };
 
 } // namespace android
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index cba5cf5..18fd857 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -275,20 +275,6 @@
     ALOGV("MediaPlayerService created");
     mNextConnId = 1;
 
-    mBatteryAudio.refCount = 0;
-    for (int i = 0; i < NUM_AUDIO_DEVICES; i++) {
-        mBatteryAudio.deviceOn[i] = 0;
-        mBatteryAudio.lastTime[i] = 0;
-        mBatteryAudio.totalTime[i] = 0;
-    }
-    // speaker is on by default
-    mBatteryAudio.deviceOn[SPEAKER] = 1;
-
-    // reset battery stats
-    // if the mediaserver has crashed, battery stats could be left
-    // in bad state, reset the state upon service start.
-    BatteryNotifier::getInstance().noteResetVideo();
-
     MediaPlayerFactory::registerBuiltinFactories();
 }
 
@@ -2042,7 +2028,7 @@
         // For now, we simply advance to the end of the VolumeShaper effect
         // if it has been started.
         if (shaper.isStarted()) {
-            operationToEnd->setXOffset(1.f);
+            operationToEnd->setNormalizedTime(1.f);
         }
         return t->applyVolumeShaper(shaper.mConfiguration, operationToEnd);
     });
@@ -2301,8 +2287,9 @@
         status = mTrack->applyVolumeShaper(configuration, operation);
         if (status >= 0) {
             (void)mVolumeHandler->applyVolumeShaper(configuration, operation);
-            // TODO: start on exact AudioTrack state (STATE_ACTIVE || STATE_STOPPING)
-            mVolumeHandler->setStarted();
+            if (mTrack->isPlaying()) { // match local AudioTrack to properly restore.
+                mVolumeHandler->setStarted();
+            }
         }
     } else {
         status = mVolumeHandler->applyVolumeShaper(configuration, operation);
@@ -2485,7 +2472,31 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void MediaPlayerService::addBatteryData(uint32_t params)
+void MediaPlayerService::addBatteryData(uint32_t params) {
+    mBatteryTracker.addBatteryData(params);
+}
+
+status_t MediaPlayerService::pullBatteryData(Parcel* reply) {
+    return mBatteryTracker.pullBatteryData(reply);
+}
+
+MediaPlayerService::BatteryTracker::BatteryTracker() {
+    mBatteryAudio.refCount = 0;
+    for (int i = 0; i < NUM_AUDIO_DEVICES; i++) {
+        mBatteryAudio.deviceOn[i] = 0;
+        mBatteryAudio.lastTime[i] = 0;
+        mBatteryAudio.totalTime[i] = 0;
+    }
+    // speaker is on by default
+    mBatteryAudio.deviceOn[SPEAKER] = 1;
+
+    // reset battery stats
+    // if the mediaserver has crashed, battery stats could be left
+    // in bad state, reset the state upon service start.
+    BatteryNotifier::getInstance().noteResetVideo();
+}
+
+void MediaPlayerService::BatteryTracker::addBatteryData(uint32_t params)
 {
     Mutex::Autolock lock(mLock);
 
@@ -2625,7 +2636,7 @@
     }
 }
 
-status_t MediaPlayerService::pullBatteryData(Parcel* reply) {
+status_t MediaPlayerService::BatteryTracker::pullBatteryData(Parcel* reply) {
     Mutex::Autolock lock(mLock);
 
     // audio output devices usage
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 009fe73..06b9cad 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -246,51 +246,62 @@
         CAMERA_PROCESS_DEATH = 4
     };
 
-    // For battery usage tracking purpose
-    struct BatteryUsageInfo {
-        // how many streams are being played by one UID
-        int     refCount;
-        // a temp variable to store the duration(ms) of audio codecs
-        // when we start a audio codec, we minus the system time from audioLastTime
-        // when we pause it, we add the system time back to the audioLastTime
-        // so after the pause, audioLastTime = pause time - start time
-        // if multiple audio streams are played (or recorded), then audioLastTime
-        // = the total playing time of all the streams
-        int32_t audioLastTime;
-        // when all the audio streams are being paused, we assign audioLastTime to
-        // this variable, so this value could be provided to the battery app
-        // in the next pullBatteryData call
-        int32_t audioTotalTime;
-
-        int32_t videoLastTime;
-        int32_t videoTotalTime;
-    };
-    KeyedVector<int, BatteryUsageInfo>    mBatteryData;
-
-    enum {
-        SPEAKER,
-        OTHER_AUDIO_DEVICE,
-        SPEAKER_AND_OTHER,
-        NUM_AUDIO_DEVICES
-    };
-
-    struct BatteryAudioFlingerUsageInfo {
-        int refCount; // how many audio streams are being played
-        int deviceOn[NUM_AUDIO_DEVICES]; // whether the device is currently used
-        int32_t lastTime[NUM_AUDIO_DEVICES]; // in ms
-        // totalTime[]: total time of audio output devices usage
-        int32_t totalTime[NUM_AUDIO_DEVICES]; // in ms
-    };
-
-    // This varialble is used to record the usage of audio output device
-    // for battery app
-    BatteryAudioFlingerUsageInfo mBatteryAudio;
-
     // Collect info of the codec usage from media player and media recorder
     virtual void                addBatteryData(uint32_t params);
     // API for the Battery app to pull the data of codecs usage
     virtual status_t            pullBatteryData(Parcel* reply);
 private:
+    struct BatteryTracker {
+        BatteryTracker();
+        // Collect info of the codec usage from media player and media recorder
+        void addBatteryData(uint32_t params);
+        // API for the Battery app to pull the data of codecs usage
+        status_t pullBatteryData(Parcel* reply);
+
+    private:
+        // For battery usage tracking purpose
+        struct BatteryUsageInfo {
+            // how many streams are being played by one UID
+            int     refCount;
+            // a temp variable to store the duration(ms) of audio codecs
+            // when we start a audio codec, we minus the system time from audioLastTime
+            // when we pause it, we add the system time back to the audioLastTime
+            // so after the pause, audioLastTime = pause time - start time
+            // if multiple audio streams are played (or recorded), then audioLastTime
+            // = the total playing time of all the streams
+            int32_t audioLastTime;
+            // when all the audio streams are being paused, we assign audioLastTime to
+            // this variable, so this value could be provided to the battery app
+            // in the next pullBatteryData call
+            int32_t audioTotalTime;
+
+            int32_t videoLastTime;
+            int32_t videoTotalTime;
+        };
+        KeyedVector<int, BatteryUsageInfo>    mBatteryData;
+
+        enum {
+            SPEAKER,
+            OTHER_AUDIO_DEVICE,
+            SPEAKER_AND_OTHER,
+            NUM_AUDIO_DEVICES
+        };
+
+        struct BatteryAudioFlingerUsageInfo {
+            int refCount; // how many audio streams are being played
+            int deviceOn[NUM_AUDIO_DEVICES]; // whether the device is currently used
+            int32_t lastTime[NUM_AUDIO_DEVICES]; // in ms
+            // totalTime[]: total time of audio output devices usage
+            int32_t totalTime[NUM_AUDIO_DEVICES]; // in ms
+        };
+
+        // This varialble is used to record the usage of audio output device
+        // for battery app
+        BatteryAudioFlingerUsageInfo mBatteryAudio;
+
+        mutable Mutex mLock;
+    };
+    BatteryTracker mBatteryTracker;
 
     class Client : public BnMediaPlayer {
         // IMediaPlayer interface
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 9fe61703..5775b43 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -1010,6 +1010,13 @@
             }
 
             // EOS
+            if (mPaused) {
+                // Do not notify EOS when paused.
+                // This is needed to avoid switch to next clip while in pause.
+                ALOGV("onDrainAudioQueue(): Do not notify EOS when paused");
+                return false;
+            }
+
             int64_t postEOSDelayUs = 0;
             if (mAudioSink->needsTrailingPadding()) {
                 postEOSDelayUs = getPendingAudioPlayoutDurationUs(ALooper::GetNowUs());
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 2877ba2..9f1be22 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -1063,6 +1063,34 @@
         return err;
     }
 
+    OMX_INDEXTYPE index;
+    err = mOMXNode->getExtensionIndex(
+            "OMX.google.android.index.AndroidNativeBufferConsumerUsage",
+            &index);
+
+    if (err != OK) {
+        // allow failure
+        err = OK;
+    } else {
+        int usageBits = 0;
+        if (nativeWindow->query(
+                nativeWindow,
+                NATIVE_WINDOW_CONSUMER_USAGE_BITS,
+                &usageBits) == OK) {
+            OMX_PARAM_U32TYPE params;
+            InitOMXParams(&params);
+            params.nPortIndex = kPortIndexOutput;
+            params.nU32 = (OMX_U32)usageBits;
+
+            err = mOMXNode->setParameter(index, &params, sizeof(params));
+
+            if (err != OK) {
+                ALOGE("Fail to set AndroidNativeBufferConsumerUsage: %d", err);
+                return err;
+            }
+        }
+    }
+
     OMX_U32 usage = 0;
     err = mOMXNode->getGraphicBufferUsage(kPortIndexOutput, &usage);
     if (err != 0) {
@@ -1106,7 +1134,8 @@
 
     if (err == OK) {
         err = setupNativeWindowSizeFormatAndUsage(
-                mNativeWindow.get(), &mNativeWindowUsageBits, preregister /* reconnect */);
+                mNativeWindow.get(), &mNativeWindowUsageBits,
+                preregister && !mTunneled /* reconnect */);
     }
     if (err != OK) {
         mNativeWindowUsageBits = 0;
@@ -5401,6 +5430,8 @@
             ALOGE_IF("[%s] failed to release codec instance: err=%d",
                        mCodec->mComponentName.c_str(), err);
             mCodec->mCallback->onReleaseCompleted();
+
+            mCodec->changeState(mCodec->mUninitializedState);
             break;
         }
 
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cafedba..4f18a26 100755
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -72,8 +72,10 @@
 static const int64_t kMaxMetadataSize = 0x4000000LL;   // 64MB max per-frame metadata size
 
 static const char kMetaKey_Version[]    = "com.android.version";
-#ifdef SHOW_MODEL_BUILD
+static const char kMetaKey_Manufacturer[]      = "com.android.manufacturer";
 static const char kMetaKey_Model[]      = "com.android.model";
+
+#ifdef SHOW_BUILD
 static const char kMetaKey_Build[]      = "com.android.build";
 #endif
 static const char kMetaKey_CaptureFps[] = "com.android.capture.fps";
@@ -93,7 +95,7 @@
     kHevcNalUnitTypePrefixSei,
     kHevcNalUnitTypeSuffixSei,
 };
-/* uncomment to include model and build in meta */
+/* uncomment to include build in meta */
 //#define SHOW_MODEL_BUILD 1
 
 class MPEG4Writer::Track {
@@ -611,12 +613,20 @@
         mMetaKeys->setString(kMetaKey_Version, val, n + 1);
         mMoovExtraSize += sizeof(kMetaKey_Version) + n + 32;
     }
-#ifdef SHOW_MODEL_BUILD
-    if (property_get("ro.product.model", val, NULL)
-            && (n = strlen(val)) > 0) {
-        mMetaKeys->setString(kMetaKey_Model, val, n + 1);
-        mMoovExtraSize += sizeof(kMetaKey_Model) + n + 32;
+
+    if (property_get_bool("media.recorder.show_manufacturer_and_model", false)) {
+        if (property_get("ro.product.manufacturer", val, NULL)
+                && (n = strlen(val)) > 0) {
+            mMetaKeys->setString(kMetaKey_Manufacturer, val, n + 1);
+            mMoovExtraSize += sizeof(kMetaKey_Manufacturer) + n + 32;
+        }
+        if (property_get("ro.product.model", val, NULL)
+                && (n = strlen(val)) > 0) {
+            mMetaKeys->setString(kMetaKey_Model, val, n + 1);
+            mMoovExtraSize += sizeof(kMetaKey_Model) + n + 32;
+        }
     }
+#ifdef SHOW_MODEL_BUILD
     if (property_get("ro.build.display.id", val, NULL)
             && (n = strlen(val)) > 0) {
         mMetaKeys->setString(kMetaKey_Build, val, n + 1);
diff --git a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
index b057ffe..b1af17b 100644
--- a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
@@ -613,6 +613,7 @@
     IV_STATUS_T status;
     WORD32 level;
     uint32_t displaySizeY;
+
     CHECK(!mStarted);
 
     OMX_ERRORTYPE errType = OMX_ErrorNone;
@@ -916,6 +917,9 @@
         }
     }
 
+    // clear other pointers into the space being free()d
+    mCodecCtx = NULL;
+
     mStarted = false;
 
     return OMX_ErrorNone;
@@ -1508,6 +1512,14 @@
     return;
 }
 
+void SoftAVC::onReset() {
+    SoftVideoEncoderOMXComponent::onReset();
+
+    if (releaseEncoder() != OMX_ErrorNone) {
+        ALOGW("releaseEncoder failed");
+    }
+}
+
 }  // namespace android
 
 android::SoftOMXComponent *createSoftOMXComponent(
diff --git a/media/libstagefright/codecs/avcenc/SoftAVCEnc.h b/media/libstagefright/codecs/avcenc/SoftAVCEnc.h
index 4d30ba0..818e4a1 100644
--- a/media/libstagefright/codecs/avcenc/SoftAVCEnc.h
+++ b/media/libstagefright/codecs/avcenc/SoftAVCEnc.h
@@ -136,6 +136,8 @@
 protected:
     virtual ~SoftAVC();
 
+    virtual void onReset();
+
 private:
     enum {
         kNumBuffers = 2,
diff --git a/media/libstagefright/omx/1.0/Omx.cpp b/media/libstagefright/omx/1.0/Omx.cpp
index 365ea5d..64b2c08 100644
--- a/media/libstagefright/omx/1.0/Omx.cpp
+++ b/media/libstagefright/omx/1.0/Omx.cpp
@@ -180,26 +180,22 @@
         return OK;
     }
 
-    wp<IBase> observer;
     {
         Mutex::Autolock autoLock(mLock);
         ssize_t observerIndex = mNode2Observer.indexOfKey(instance.get());
-        if (observerIndex < 0) {
-            return OK;
-        }
-        observer = mNode2Observer.valueAt(observerIndex);
-        ssize_t nodeIndex = mLiveNodes.indexOfKey(observer);
-        if (nodeIndex < 0) {
-            return OK;
-        }
-        mNode2Observer.removeItemsAt(observerIndex);
-        mLiveNodes.removeItemsAt(nodeIndex);
-    }
-
-    {
-        sp<IBase> sObserver = observer.promote();
-        if (sObserver != nullptr) {
-            sObserver->unlinkToDeath(this);
+        if (observerIndex >= 0) {
+            wp<IBase> observer = mNode2Observer.valueAt(observerIndex);
+            ssize_t nodeIndex = mLiveNodes.indexOfKey(observer);
+            if (nodeIndex >= 0) {
+                mNode2Observer.removeItemsAt(observerIndex);
+                mLiveNodes.removeItemsAt(nodeIndex);
+                sp<IBase> sObserver = observer.promote();
+                if (sObserver != nullptr) {
+                    sObserver->unlinkToDeath(this);
+                }
+            } else {
+                LOG(WARNING) << "Inconsistent observer record";
+            }
         }
     }
 
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferProducer.cpp b/media/libstagefright/omx/1.0/WGraphicBufferProducer.cpp
index 36bd624..650db8e 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferProducer.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferProducer.cpp
@@ -248,7 +248,7 @@
 
 Return<int32_t> TWGraphicBufferProducer::setSidebandStream(const hidl_handle& stream) {
     return static_cast<int32_t>(mBase->setSidebandStream(NativeHandle::create(
-            native_handle_clone(stream), true)));
+            stream ? native_handle_clone(stream) : NULL, true)));
 }
 
 Return<void> TWGraphicBufferProducer::allocateBuffers(
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index d0f64ca..db99ef2 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -1861,7 +1861,7 @@
 }
 
 void OMXNodeInstance::codecBufferFilled(omx_message &msg) {
-    Mutex::Autolock autoLock(mBufferIDLock);
+    Mutex::Autolock autoLock(mLock);
 
     if (mMaxTimestampGapUs <= 0ll || mRestorePtsFailed) {
         return;
diff --git a/services/camera/libcameraservice/CameraFlashlight.cpp b/services/camera/libcameraservice/CameraFlashlight.cpp
index 836972a..e06a81f 100644
--- a/services/camera/libcameraservice/CameraFlashlight.cpp
+++ b/services/camera/libcameraservice/CameraFlashlight.cpp
@@ -93,10 +93,6 @@
     }
 
     if (mFlashControl == NULL) {
-        if (enabled == false) {
-            return OK;
-        }
-
         res = createFlashlightControl(cameraId);
         if (res) {
             return res;
@@ -139,10 +135,14 @@
         cameraIds[i] = String8(ids[i].c_str());
     }
 
-    mHasFlashlightMap.clear();
-    mFlashlightMapInitialized = false;
+    mFlashControl.clear();
 
     for (auto &id : cameraIds) {
+        ssize_t index = mHasFlashlightMap.indexOfKey(id);
+        if (0 <= index) {
+            continue;
+        }
+
         bool hasFlash = false;
         res = createFlashlightControl(id);
         if (res) {
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 0031441..415fdf5 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -198,13 +198,17 @@
 }
 
 status_t CameraService::enumerateProviders() {
-    mCameraProviderManager = new CameraProviderManager();
     status_t res;
-    res = mCameraProviderManager->initialize(this);
-    if (res != OK) {
-        ALOGE("%s: Unable to initialize camera provider manager: %s (%d)",
-                __FUNCTION__, strerror(-res), res);
-        return res;
+    Mutex::Autolock l(mServiceLock);
+
+    if (nullptr == mCameraProviderManager.get()) {
+        mCameraProviderManager = new CameraProviderManager();
+        res = mCameraProviderManager->initialize(this);
+        if (res != OK) {
+            ALOGE("%s: Unable to initialize camera provider manager: %s (%d)",
+                    __FUNCTION__, strerror(-res), res);
+            return res;
+        }
     }
 
     mNumberOfCameras = mCameraProviderManager->getCameraCount();
@@ -216,15 +220,25 @@
     // TODO: maybe put this into CameraProviderManager::initialize()?
     mCameraProviderManager->setUpVendorTags();
 
-    mFlashlight = new CameraFlashlight(mCameraProviderManager, this);
+    if (nullptr == mFlashlight.get()) {
+        mFlashlight = new CameraFlashlight(mCameraProviderManager, this);
+    }
+
     res = mFlashlight->findFlashUnits();
     if (res != OK) {
         ALOGE("Failed to enumerate flash units: %s (%d)", strerror(-res), res);
     }
 
-    // TODO: Verify device versions are in support
-
     for (auto& cameraId : mCameraProviderManager->getCameraDeviceIds()) {
+        String8 id8 = String8(cameraId.c_str());
+        {
+            Mutex::Autolock lock(mCameraStatesLock);
+            auto iter = mCameraStates.find(id8);
+            if (iter != mCameraStates.end()) {
+                continue;
+            }
+        }
+
         hardware::camera::common::V1_0::CameraResourceCost cost;
         res = mCameraProviderManager->getResourceCost(cameraId, &cost);
         if (res != OK) {
@@ -235,22 +249,19 @@
         for (size_t i = 0; i < cost.conflictingDevices.size(); i++) {
             conflicting.emplace(String8(cost.conflictingDevices[i].c_str()));
         }
-        String8 id8 = String8(cameraId.c_str());
 
         Mutex::Autolock lock(mCameraStatesLock);
         mCameraStates.emplace(id8,
             std::make_shared<CameraState>(id8, cost.resourceCost, conflicting));
 
         if (mFlashlight->hasFlashUnit(id8)) {
-            mTorchStatusMap.add(id8,
-                TorchModeStatus::AVAILABLE_OFF);
+            mTorchStatusMap.add(id8, TorchModeStatus::AVAILABLE_OFF);
         }
     }
 
     return OK;
 }
 
-
 sp<ICameraServiceProxy> CameraService::getCameraServiceProxy() {
     sp<ICameraServiceProxy> proxyBinder = nullptr;
 #ifndef __BRILLO__
@@ -276,6 +287,10 @@
     VendorTagDescriptor::clearGlobalVendorTagDescriptor();
 }
 
+void CameraService::onNewProviderRegistered() {
+    enumerateProviders();
+}
+
 void CameraService::onDeviceStatusChanged(const String8& id,
         CameraDeviceStatus newHalStatus) {
     ALOGI("%s: Status changed for cameraId=%s, newStatus=%d", __FUNCTION__,
@@ -407,6 +422,7 @@
 
 Status CameraService::getNumberOfCameras(int32_t type, int32_t* numCameras) {
     ATRACE_CALL();
+    Mutex::Autolock l(mServiceLock);
     switch (type) {
         case CAMERA_TYPE_BACKWARD_COMPATIBLE:
             *numCameras = mNumberOfNormalCameras;
@@ -426,6 +442,8 @@
 Status CameraService::getCameraInfo(int cameraId,
         CameraInfo* cameraInfo) {
     ATRACE_CALL();
+    Mutex::Autolock l(mServiceLock);
+
     if (!mInitialized) {
         return STATUS_ERROR(ERROR_DISCONNECTED,
                 "Camera subsystem is not available");
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 7d81993..87603a3 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -99,6 +99,7 @@
             hardware::camera::common::V1_0::CameraDeviceStatus newHalStatus) override;
     virtual void        onTorchStatusChanged(const String8& cameraId,
             hardware::camera::common::V1_0::TorchModeStatus newStatus) override;
+    virtual void        onNewProviderRegistered() override;
 
     /////////////////////////////////////////////////////////////////////
     // ICameraService
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 38fe1b6..65633db 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -231,7 +231,6 @@
 status_t CameraProviderManager::setUpVendorTags() {
     sp<VendorTagDescriptorCache> tagCache = new VendorTagDescriptorCache();
 
-    VendorTagDescriptorCache::clearGlobalVendorTagCache();
     for (auto& provider : mProviders) {
         hardware::hidl_vec<VendorTagSection> vts;
         Status status;
@@ -331,9 +330,17 @@
         const hardware::hidl_string& /*fqName*/,
         const hardware::hidl_string& name,
         bool /*preexisting*/) {
-    std::lock_guard<std::mutex> lock(mInterfaceMutex);
+    {
+        std::lock_guard<std::mutex> lock(mInterfaceMutex);
 
-    addProviderLocked(name);
+        addProviderLocked(name);
+    }
+
+    sp<StatusListener> listener = getStatusListener();
+    if (nullptr != listener.get()) {
+        listener->onNewProviderRegistered();
+    }
+
     return hardware::Return<void>();
 }
 
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 3afc1d9..9ba7f91 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -106,6 +106,7 @@
                 hardware::camera::common::V1_0::CameraDeviceStatus newStatus) = 0;
         virtual void onTorchStatusChanged(const String8 &cameraId,
                 hardware::camera::common::V1_0::TorchModeStatus newStatus) = 0;
+        virtual void onNewProviderRegistered() = 0;
     };
 
     /**
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 4706319..4571db8 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -4080,6 +4080,13 @@
         }
 
         for (size_t i = 0; i < halRequest->num_output_buffers; i++) {
+            //Buffers that failed processing could still have
+            //valid acquire fence.
+            int acquireFence = (*outputBuffers)[i].acquire_fence;
+            if (0 <= acquireFence) {
+                close(acquireFence);
+                outputBuffers->editItemAt(i).acquire_fence = -1;
+            }
             outputBuffers->editItemAt(i).status = CAMERA3_BUFFER_STATUS_ERROR;
             captureRequest->mOutputStreams.editItemAt(i)->returnBuffer((*outputBuffers)[i], 0);
         }
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index f7197af..9e2813e 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -5,7 +5,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-    main_mediametrics.cpp          \
+    main_mediametrics.cpp              \
+    MetricsSummarizerCodec.cpp         \
+    MetricsSummarizerExtractor.cpp     \
+    MetricsSummarizerPlayer.cpp        \
+    MetricsSummarizerRecorder.cpp      \
+    MetricsSummarizer.cpp              \
     MediaAnalyticsService.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 35c1f5b..876c685 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
 #define LOG_TAG "MediaAnalyticsService"
 #include <utils/Log.h>
 
+#include <stdint.h>
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -70,11 +71,28 @@
 
 #include "MediaAnalyticsService.h"
 
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+#include "MetricsSummarizerExtractor.h"
+#include "MetricsSummarizerPlayer.h"
+#include "MetricsSummarizerRecorder.h"
+
 
 namespace android {
 
 
-#define DEBUG_QUEUE     0
+
+// summarized records
+// up to 48 sets, each covering an hour -- at least 2 days of coverage
+// (will be longer if there are hours without any media action)
+static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
+static const int kMaxRecordSets = 48;
+// individual records kept in memory
+static const int kMaxRecords    = 100;
+
+
+static const char *kServiceName = "media.metrics";
+
 
 //using android::status_t;
 //using android::OK;
@@ -85,18 +103,67 @@
 
 void MediaAnalyticsService::instantiate() {
     defaultServiceManager()->addService(
-            String16("media.metrics"), new MediaAnalyticsService());
+            String16(kServiceName), new MediaAnalyticsService());
 }
 
-// XXX: add dynamic controls for mMaxRecords
+// handle sets of summarizers
+MediaAnalyticsService::SummarizerSet::SummarizerSet() {
+    mSummarizers = new List<MetricsSummarizer *>();
+}
+MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
+    // empty the list
+    List<MetricsSummarizer *> *l = mSummarizers;
+    while (l->size() > 0) {
+        MetricsSummarizer *summarizer = *(l->begin());
+        l->erase(l->begin());
+        delete summarizer;
+    }
+}
+
+void MediaAnalyticsService::newSummarizerSet() {
+    ALOGD("MediaAnalyticsService::newSummarizerSet");
+    MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
+    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    set->setStarted(now);
+
+    set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
+    set->appendSummarizer(new MetricsSummarizerCodec("codec"));
+    set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
+    set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
+
+    // ALWAYS at the end, since it catches everything
+    set->appendSummarizer(new MetricsSummarizer(NULL));
+
+    // inject this set at the BACK of the list.
+    mSummarizerSets->push_back(set);
+    mCurrentSet = set;
+
+    // limit the # that we have
+    if (mMaxRecordSets > 0) {
+        List<SummarizerSet *> *l = mSummarizerSets;
+        while (l->size() > (size_t) mMaxRecordSets) {
+            ALOGD("Deleting oldest record set....");
+            MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
+            l->erase(l->begin());
+            delete oset;
+            mSetsDiscarded++;
+        }
+    }
+}
+
 MediaAnalyticsService::MediaAnalyticsService()
-        : mMaxRecords(100) {
+        : mMaxRecords(kMaxRecords),
+          mMaxRecordSets(kMaxRecordSets),
+          mNewSetInterval(kNewSetIntervalNs) {
 
     ALOGD("MediaAnalyticsService created");
     // clear our queues
     mOpen = new List<MediaAnalyticsItem *>();
     mFinalized = new List<MediaAnalyticsItem *>();
 
+    mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
+    newSummarizerSet();
+
     mItemsSubmitted = 0;
     mItemsFinalized = 0;
     mItemsDiscarded = 0;
@@ -109,7 +176,13 @@
 MediaAnalyticsService::~MediaAnalyticsService() {
         ALOGD("MediaAnalyticsService destroyed");
 
-    // XXX: clean out mOpen and mFinalized
+    // clean out mOpen and mFinalized
+    delete mOpen;
+    mOpen = NULL;
+    delete mFinalized;
+    mFinalized = NULL;
+
+    // XXX: clean out the summaries
 }
 
 
@@ -145,7 +218,7 @@
         case AID_MEDIA_EX:
         case AID_MEDIA_DRM:
             // trusted source, only override default values
-            isTrusted = true;
+                isTrusted = true;
             if (uid_given == (-1)) {
                 item->setUid(uid);
             }
@@ -197,10 +270,12 @@
                 oitem = NULL;
             } else {
                 oitem->setFinalized(true);
+                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
             }
             // new record could itself be marked finalized...
             if (finalizing) {
+                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             } else {
@@ -211,6 +286,7 @@
             // combine the records, send it to finalized if appropriate
             oitem->merge(item);
             if (finalizing) {
+                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
                 mItemsFinalized++;
             }
@@ -229,6 +305,7 @@
                 delete item;
                 item = NULL;
             } else {
+                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             }
@@ -239,26 +316,6 @@
     return id;
 }
 
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
-    // this might never get called; the binder interface maps to the full parm list
-    // on the client side before making the binder call.
-    // but this lets us be sure...
-    List<MediaAnalyticsItem*> *list;
-    list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
-    return list;
-}
-
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
-
-    // XXX: implement the get-item-list semantics
-
-    List<MediaAnalyticsItem *> *list = NULL;
-    // set up our query on the persistent data
-    // slurp in all of the pieces
-    // return that
-    return list;
-}
-
 status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
 {
     const size_t SIZE = 512;
@@ -277,15 +334,21 @@
 
     // crack any parameters
     bool clear = false;
+    bool summary = false;
     nsecs_t ts_since = 0;
+    String16 summaryOption("-summary");
     String16 clearOption("-clear");
     String16 sinceOption("-since");
     String16 helpOption("-help");
+    String16 onlyOption("-only");
+    const char *only = NULL;
     int n = args.size();
     for (int i = 0; i < n; i++) {
         String8 myarg(args[i]);
         if (args[i] == clearOption) {
             clear = true;
+        } else if (args[i] == summaryOption) {
+            summary = true;
         } else if (args[i] == sinceOption) {
             i++;
             if (i < n) {
@@ -301,12 +364,27 @@
             }
             // command line is milliseconds; internal units are nano-seconds
             ts_since *= 1000*1000;
+        } else if (args[i] == onlyOption) {
+            i++;
+            if (i < n) {
+                String8 value(args[i]);
+                const char *p = value.string();
+                char *q = strdup(p);
+                if (q != NULL) {
+                    if (only != NULL) {
+                        free((void*)only);
+                    }
+                only = q;
+                }
+            }
         } else if (args[i] == helpOption) {
             result.append("Recognized parameters:\n");
             result.append("-help        this help message\n");
+            result.append("-summary     show summary info\n");
             result.append("-clear       clears out saved records\n");
-            result.append("-since XXX   include records since XXX\n");
-            result.append("             (XXX is milliseconds since the UNIX epoch)\n");
+            result.append("-only X      process records for component X\n");
+            result.append("-since X     include records since X\n");
+            result.append("             (X is milliseconds since the UNIX epoch)\n");
             write(fd, result.string(), result.size());
             return NO_ERROR;
         }
@@ -314,9 +392,42 @@
 
     Mutex::Autolock _l(mLock);
 
-    snprintf(buffer, SIZE, "Dump of the mediametrics process:\n");
+    // we ALWAYS dump this piece
+    snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
     result.append(buffer);
 
+    dumpHeaders(result, ts_since);
+
+    // only want 1, to avoid confusing folks that parse the output
+    if (summary) {
+        dumpSummaries(result, ts_since, only);
+    } else {
+        dumpRecent(result, ts_since, only);
+    }
+
+
+    if (clear) {
+        // remove everything from the finalized queue
+        while (mFinalized->size() > 0) {
+            MediaAnalyticsItem * oitem = *(mFinalized->begin());
+            mFinalized->erase(mFinalized->begin());
+            delete oitem;
+            mItemsDiscarded++;
+        }
+
+        // shall we clear the summary data too?
+
+    }
+
+    write(fd, result.string(), result.size());
+    return NO_ERROR;
+}
+
+// dump headers
+void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
+
     int enabled = MediaAnalyticsItem::isEnabled();
     if (enabled) {
         snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
@@ -331,50 +442,71 @@
         " Discarded: %" PRId64 "\n",
         mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
     result.append(buffer);
+    snprintf(buffer, SIZE,
+        "Summary Sets Discarded: %" PRId64 "\n", mSetsDiscarded);
+    result.append(buffer);
     if (ts_since != 0) {
         snprintf(buffer, SIZE,
             "Dumping Queue entries more recent than: %" PRId64 "\n",
             (int64_t) ts_since);
         result.append(buffer);
     }
+}
+
+// dump summary info
+void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
+    int slot = 0;
+
+    snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
+    result.append(buffer);
+
+    // have each of the distillers dump records
+    if (mSummarizerSets != NULL) {
+        List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
+        for (; itSet != mSummarizerSets->end(); itSet++) {
+            nsecs_t when = (*itSet)->getStarted();
+            if (when < ts_since) {
+                continue;
+            }
+            List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
+            List<MetricsSummarizer *>::iterator it = list->begin();
+            for (; it != list->end(); it++) {
+                if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
+                    ALOGV("Told to omit '%s'", (*it)->getKey());
+                }
+                AString distilled = (*it)->dumpSummary(slot, only);
+                result.append(distilled.c_str());
+            }
+        }
+    }
+}
+
+// the recent, detailed queues
+void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
 
     // show the recently recorded records
     snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
     result.append(buffer);
-    result.append(this->dumpQueue(mFinalized, ts_since));
+    result.append(this->dumpQueue(mFinalized, ts_since, only));
 
     snprintf(buffer, sizeof(buffer), "\nIn-Progress Metrics (newest first):\n");
     result.append(buffer);
-    result.append(this->dumpQueue(mOpen, ts_since));
+    result.append(this->dumpQueue(mOpen, ts_since, only));
 
     // show who is connected and injecting records?
     // talk about # records fed to the 'readers'
     // talk about # records we discarded, perhaps "discarded w/o reading" too
-
-    if (clear) {
-        // remove everything from the finalized queue
-        while (mFinalized->size() > 0) {
-            MediaAnalyticsItem * oitem = *(mFinalized->begin());
-            if (DEBUG_QUEUE) {
-                ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
-                    oitem->getKey().c_str(), oitem->getSessionID(),
-                    oitem->getTimestamp());
-            }
-            mFinalized->erase(mFinalized->begin());
-            mItemsDiscarded++;
-        }
-    }
-
-    write(fd, result.string(), result.size());
-    return NO_ERROR;
 }
-
 // caller has locked mLock...
 String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList) {
-    return dumpQueue(theList, (nsecs_t) 0);
+    return dumpQueue(theList, (nsecs_t) 0, NULL);
 }
 
-String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since) {
+String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since, const char * only) {
     String8 result;
     int slot = 0;
 
@@ -387,6 +519,11 @@
             if (when < ts_since) {
                 continue;
             }
+            if (only != NULL &&
+                strcmp(only, (*it)->getKey().c_str()) != 0) {
+                ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
+                continue;
+            }
             AString entry = (*it)->toString();
             result.appendFormat("%5d: %s\n", slot, entry.c_str());
             slot++;
@@ -405,13 +542,6 @@
 
     Mutex::Autolock _l(mLock);
 
-    if (DEBUG_QUEUE) {
-        ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
-            item->getSessionID(), item->getTimestamp());
-        String8 before = dumpQueue(l);
-        ALOGD("Q before insert: %s", before.string());
-    }
-
     // adding at back of queue (fifo order)
     if (front)  {
         l->push_front(item);
@@ -419,30 +549,15 @@
         l->push_back(item);
     }
 
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after insert: %s", after.string());
-    }
-
     // keep removing old records the front until we're in-bounds
     if (mMaxRecords > 0) {
         while (l->size() > (size_t) mMaxRecords) {
             MediaAnalyticsItem * oitem = *(l->begin());
-            if (DEBUG_QUEUE) {
-                ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
-                    oitem->getKey().c_str(), oitem->getSessionID(),
-                    oitem->getTimestamp());
-            }
             l->erase(l->begin());
             delete oitem;
             mItemsDiscarded++;
         }
     }
-
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after cleanup: %s", after.string());
-    }
 }
 
 // are they alike enough that nitem can be folded into oitem?
@@ -515,29 +630,14 @@
 
     Mutex::Autolock _l(mLock);
 
-    if(DEBUG_QUEUE) {
-        String8 before = dumpQueue(l);
-        ALOGD("Q before delete: %s", before.string());
-    }
-
     for (List<MediaAnalyticsItem *>::iterator it = l->begin();
         it != l->end(); it++) {
         if ((*it)->getSessionID() != item->getSessionID())
             continue;
-
-        if (DEBUG_QUEUE) {
-            ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
-            ALOGD("drop record at %s:%d", __FILE__, __LINE__);
-        }
         delete *it;
         l->erase(it);
         break;
     }
-
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after delete: %s", after.string());
-    }
 }
 
 static AString allowedKeys[] =
@@ -579,5 +679,43 @@
     return false;
 }
 
+// insert into the appropriate summarizer.
+// we make our own copy to save/summarize
+void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
+
+    ALOGV("MediaAnalyticsService::summarize()");
+
+    if (item == NULL) {
+        return;
+    }
+
+    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    if (mCurrentSet == NULL
+        || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
+        newSummarizerSet();
+    }
+
+    if (mCurrentSet == NULL) {
+        return;
+    }
+
+    List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
+    List<MetricsSummarizer *>::iterator it = summarizers->begin();
+    for (; it != summarizers->end(); it++) {
+        if ((*it)->isMine(*item)) {
+            break;
+        }
+    }
+    if (it == summarizers->end()) {
+        ALOGD("no handler for type %s", item->getKey().c_str());
+        return;               // no handler
+    }
+
+    // invoke the summarizer. summarizer will make whatever copies
+    // it wants; the caller retains ownership of item.
+
+    (*it)->handleRecord(item);
+
+}
 
 } // namespace android
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index d2b0f09..6685967 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
 
 #include <media/IMediaAnalyticsService.h>
 
+#include "MetricsSummarizer.h"
+
 
 namespace android {
 
@@ -39,12 +41,6 @@
     // on this side, caller surrenders ownership
     virtual int64_t submit(MediaAnalyticsItem *item, bool forcenew);
 
-    virtual List<MediaAnalyticsItem *>
-            *getMediaAnalyticsItemList(bool finished, int64_t ts);
-    virtual List<MediaAnalyticsItem *>
-            *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key);
-
-
     static  void            instantiate();
     virtual status_t        dump(int fd, const Vector<String16>& args);
 
@@ -58,6 +54,7 @@
     int64_t mItemsSubmitted;
     int64_t mItemsFinalized;
     int64_t mItemsDiscarded;
+    int64_t mSetsDiscarded;
     MediaAnalyticsItem::SessionID_t mLastSessionID;
 
     // partitioned a bit so we don't over serialize
@@ -67,6 +64,10 @@
     // the most we hold in memory
     // up to this many in each queue (open, finalized)
     int32_t mMaxRecords;
+    // # of sets of summaries
+    int32_t mMaxRecordSets;
+    // nsecs until we start a new record set
+    nsecs_t mNewSetInterval;
 
     // input validation after arrival from client
     bool contentValid(MediaAnalyticsItem *item, bool isTrusted);
@@ -82,12 +83,47 @@
     MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
                                      MediaAnalyticsItem *, bool removeit);
 
+    // summarizers
+    void summarize(MediaAnalyticsItem *item);
+    class SummarizerSet {
+        nsecs_t mStarted;
+        List<MetricsSummarizer *> *mSummarizers;
+
+      public:
+        void appendSummarizer(MetricsSummarizer *s) {
+            if (s) {
+                mSummarizers->push_back(s);
+            }
+        };
+        nsecs_t getStarted() { return mStarted;}
+        void setStarted(nsecs_t started) {mStarted = started;}
+        List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
+
+        SummarizerSet();
+        ~SummarizerSet();
+    };
+    void newSummarizerSet();
+    List<SummarizerSet *> *mSummarizerSets;
+    SummarizerSet *mCurrentSet;
+    List<MetricsSummarizer *> *getFirstSet() {
+        List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
+        if (first != mSummarizerSets->end()) {
+            return (*first)->getSummarizers();
+        }
+        return NULL;
+    }
+
     void saveItem(MediaAnalyticsItem);
     void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
     void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
 
+    // support for generating output
     String8 dumpQueue(List<MediaAnalyticsItem*> *);
-    String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t);
+    String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t, const char *only);
+
+    void dumpHeaders(String8 &result, nsecs_t ts_since);
+    void dumpSummaries(String8 &result, nsecs_t ts_since, const char * only);
+    void dumpRecent(String8 &result, nsecs_t ts_since, const char * only);
 
 };
 
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
new file mode 100644
index 0000000..6d5787e
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MetricsSummarizer"
+#include <utils/Log.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+#define DEBUG_SORT      0
+#define DEBUG_QUEUE     0
+
+
+MetricsSummarizer::MetricsSummarizer(const char *key)
+    : mIgnorables(NULL)
+{
+    ALOGV("MetricsSummarizer::MetricsSummarizer");
+
+    if (key == NULL) {
+        mKey = key;
+    } else {
+        mKey = strdup(key);
+    }
+
+    mSummaries = new List<MediaAnalyticsItem *>();
+}
+
+MetricsSummarizer::~MetricsSummarizer()
+{
+    ALOGV("MetricsSummarizer::~MetricsSummarizer");
+    if (mKey) {
+        free((void *)mKey);
+        mKey = NULL;
+    }
+
+    // clear the list of items we have saved
+    while (mSummaries->size() > 0) {
+        MediaAnalyticsItem * oitem = *(mSummaries->begin());
+        if (DEBUG_QUEUE) {
+            ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
+                oitem->getKey().c_str(), oitem->getSessionID(),
+                oitem->getTimestamp());
+        }
+        mSummaries->erase(mSummaries->begin());
+        delete oitem;
+    }
+}
+
+// so we know what summarizer we were using
+const char *MetricsSummarizer::getKey() {
+    const char *value = mKey;
+    if (value == NULL) {
+        value = "unknown";
+    }
+    return value;
+}
+
+// should the record be given to this summarizer
+bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
+{
+    if (mKey == NULL)
+        return true;
+    AString itemKey = item.getKey();
+    if (strcmp(mKey, itemKey.c_str()) != 0) {
+        return false;
+    }
+    return true;
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot)
+{
+    return dumpSummary(slot, NULL);
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
+{
+    AString value = "";
+
+    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+    if (it != mSummaries->end()) {
+        char buf[16];   // enough for "#####: "
+        for (; it != mSummaries->end(); it++) {
+            if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
+                continue;
+            }
+            AString entry = (*it)->toString();
+            snprintf(buf, sizeof(buf), "%5d: ", slot);
+            value.append(buf);
+            value.append(entry.c_str());
+            value.append("\n");
+            slot++;
+        }
+    }
+    return value;
+}
+
+void MetricsSummarizer::setIgnorables(const char **ignorables) {
+    mIgnorables = ignorables;
+}
+
+const char **MetricsSummarizer::getIgnorables() {
+    return mIgnorables;
+}
+
+void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
+
+    ALOGV("MetricsSummarizer::handleRecord() for %s",
+                item == NULL ? "<nothing>" : item->toString().c_str());
+
+    if (item == NULL) {
+        return;
+    }
+
+    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+    for (; it != mSummaries->end(); it++) {
+        bool good = sameAttributes((*it), item, getIgnorables());
+        ALOGV("Match against %s says %d",
+              (*it)->toString().c_str(), good);
+        if (good)
+            break;
+    }
+    if (it == mSummaries->end()) {
+            ALOGV("save new record");
+            item = item->dup();
+            if (item == NULL) {
+                ALOGE("unable to save MediaMetrics record");
+            }
+            sortProps(item);
+            item->setInt32("aggregated",1);
+            mSummaries->push_back(item);
+    } else {
+            ALOGV("increment existing record");
+            (*it)->addInt32("aggregated",1);
+            mergeRecord(*(*it), *item);
+    }
+}
+
+void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
+    // default is no further massaging.
+    ALOGV("MetricsSummarizer::mergeRecord() [default]");
+    return;
+}
+
+
+//
+// Comparators
+//
+
+// testing that all of 'single' is in 'summ'
+// and that the values match.
+// 'summ' may have extra fields.
+// 'ignorable' is a set of things that we don't worry about matching up
+// (usually time- or count-based values we'll sum elsewhere)
+bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+    if (single == NULL || summ == NULL) {
+        return false;
+    }
+    ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
+    ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
+
+    // this can be made better.
+    for(size_t i=0;i<single->mPropCount;i++) {
+        MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
+        const char *attrName = prop1->mName;
+        ALOGV("compare on attr '%s'", attrName);
+
+        // is it something we should ignore
+        if (ignorable != NULL) {
+            const char **ig = ignorable;
+            while (*ig) {
+                if (strcmp(*ig, attrName) == 0) {
+                    break;
+                }
+                ig++;
+            }
+            if (*ig) {
+                ALOGV("we don't mind that it has attr '%s'", attrName);
+                continue;
+            }
+        }
+
+        MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
+        if (prop2 == NULL) {
+            ALOGV("summ doesn't have this attr");
+            return false;
+        }
+        if (prop1->mType != prop2->mType) {
+            ALOGV("mismatched attr types");
+            return false;
+        }
+        switch (prop1->mType) {
+            case MediaAnalyticsItem::kTypeInt32:
+                if (prop1->u.int32Value != prop2->u.int32Value)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeInt64:
+                if (prop1->u.int64Value != prop2->u.int64Value)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeDouble:
+                // XXX: watch out for floating point comparisons!
+                if (prop1->u.doubleValue != prop2->u.doubleValue)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeCString:
+                if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeRate:
+                if (prop1->u.rate.count != prop2->u.rate.count)
+                    return false;
+                if (prop1->u.rate.duration != prop2->u.rate.duration)
+                    return false;
+                break;
+            default:
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+    // verify same user
+    if (summ->mPid != single->mPid)
+        return false;
+
+    // and finally do the more expensive validation of the attributes
+    return sameAttributes(summ, single, ignorable);
+}
+
+int MetricsSummarizer::PropSorter(const void *a, const void *b) {
+    MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
+    MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
+    return strcmp(ai->mName, bi->mName);
+}
+
+// we sort in the summaries so that it looks pretty in the dumpsys
+void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
+    if (item->mPropCount != 0) {
+        if (DEBUG_SORT) {
+            ALOGD("sortProps(pre): %s", item->toString().c_str());
+        }
+        qsort(item->mProps, item->mPropCount,
+              sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
+        if (DEBUG_SORT) {
+            ALOGD("sortProps(pst): %s", item->toString().c_str());
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
new file mode 100644
index 0000000..0b64eac
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZER_H
+#define ANDROID_METRICSSUMMARIZER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+
+namespace android {
+
+class MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizer(const char *key);
+    virtual ~MetricsSummarizer();
+
+    // show the key
+    const char * getKey();
+
+    // should the record be given to this summarizer
+    bool isMine(MediaAnalyticsItem &item);
+
+    // hand the record to this summarizer
+    void handleRecord(MediaAnalyticsItem *item);
+
+    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+    // dump the summarized records (for dumpsys)
+    AString dumpSummary(int &slot);
+    AString dumpSummary(int &slot, const char *only);
+
+    void setIgnorables(const char **);
+    const char **getIgnorables();
+
+ protected:
+
+    // various comparators
+    // "do these records have same attributes and values in those attrs"
+    // ditto, but watch for "error" fields
+    bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+    // attributes + from the same app/userid
+    bool sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+
+    static int PropSorter(const void *a, const void *b);
+    void sortProps(MediaAnalyticsItem *item);
+
+ private:
+    const char *mKey;
+    const char **mIgnorables;
+    List<MediaAnalyticsItem *> *mSummaries;
+
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
new file mode 100644
index 0000000..8c74782
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MetricsSummarizerCodec"
+#include <utils/Log.h>
+
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
+}
+
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
new file mode 100644
index 0000000..c01196f
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERCODEC_H
+#define ANDROID_METRICSSUMMARIZERCODEC_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerCodec : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerCodec(const char *key);
+    virtual ~MetricsSummarizerCodec() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
new file mode 100644
index 0000000..190f87d
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MetricsSummarizerExtractor"
+#include <utils/Log.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerExtractor.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
new file mode 100644
index 0000000..eee052b
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerExtractor : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerExtractor(const char *key);
+    virtual ~MetricsSummarizerExtractor() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
new file mode 100644
index 0000000..5162059
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MetricsSummarizerPlayer"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerPlayer.h"
+
+
+
+
+namespace android {
+
+static const char *player_ignorable[] = {
+    "android.media.mediaplayer.durationMs",
+    "android.media.mediaplayer.playingMs",
+    "android.media.mediaplayer.frames",
+    "android.media.mediaplayer.dropped",
+    0
+};
+
+MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
+    setIgnorables(player_ignorable);
+}
+
+void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
+
+    ALOGV("MetricsSummarizerPlayer::mergeRecord()");
+
+    //
+    // we sum time & frames.
+    // be careful about our special "-1" values that indicate 'unknown'
+    // treat those as 0 [basically, not summing them into the totals].
+    int64_t duration = 0;
+    if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
+        ALOGV("found durationMs of %" PRId64, duration);
+        summation.addInt64("android.media.mediaplayer.durationMs",duration);
+    }
+    int64_t playing = 0;
+    if (item.getInt64("android.media.mediaplayer.playingMs", &playing))
+        ALOGV("found playingMs of %" PRId64, playing);
+        if (playing >= 0) {
+            summation.addInt64("android.media.mediaplayer.playingMs",playing);
+        }
+    int64_t frames = 0;
+    if (item.getInt64("android.media.mediaplayer.frames", &frames))
+        ALOGV("found framess of %" PRId64, frames);
+        if (frames >= 0) {
+            summation.addInt64("android.media.mediaplayer.frames",frames);
+        }
+    int64_t dropped = 0;
+    if (item.getInt64("android.media.mediaplayer.dropped", &dropped))
+        ALOGV("found dropped of %" PRId64, dropped);
+        if (dropped >= 0) {
+            summation.addInt64("android.media.mediaplayer.dropped",dropped);
+        }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
new file mode 100644
index 0000000..ad1bf74
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERPLAYER_H
+#define ANDROID_METRICSSUMMARIZERPLAYER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerPlayer : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerPlayer(const char *key);
+    virtual ~MetricsSummarizerPlayer() {};
+
+    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
new file mode 100644
index 0000000..c2919c3
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MetricsSummarizerRecorder"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerRecorder.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
new file mode 100644
index 0000000..963baab
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERRECORDER_H
+#define ANDROID_METRICSSUMMARIZERRECORDER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerRecorder : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerRecorder(const char *key);
+    virtual ~MetricsSummarizerRecorder() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index d8882c9..8248f8b 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -59,6 +59,7 @@
     std::lock_guard<std::mutex> lock(mLockUpMessageQueue);
     delete mUpMessageQueue;
     mUpMessageQueue = nullptr;
+
     return AAUDIO_OK;
 }
 
diff --git a/services/oboeservice/AAudioServiceStreamBase.h b/services/oboeservice/AAudioServiceStreamBase.h
index d6b6ee3..9318c2e 100644
--- a/services/oboeservice/AAudioServiceStreamBase.h
+++ b/services/oboeservice/AAudioServiceStreamBase.h
@@ -133,9 +133,6 @@
     // This is used by one thread to tell another thread to exit. So it must be atomic.
     std::atomic<bool>   mThreadEnabled;
 
-
-    int                mAudioDataFileDescriptor = -1;
-
     aaudio_audio_format_t mAudioFormat = AAUDIO_FORMAT_UNSPECIFIED;
     int32_t            mFramesPerBurst = 0;
     int32_t            mSamplesPerFrame = AAUDIO_UNSPECIFIED;
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.cpp b/services/oboeservice/AAudioServiceStreamMMAP.cpp
index 5e4a9b1..cadc2a4 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.cpp
+++ b/services/oboeservice/AAudioServiceStreamMMAP.cpp
@@ -60,6 +60,13 @@
     // FIXME Make closing synchronous.
     AudioClock::sleepForNanos(100 * AAUDIO_NANOS_PER_MILLISECOND);
 
+    if (mAudioDataFileDescriptor != -1) {
+        ALOGV("AAudioServiceStreamMMAP: LEAK? close(mAudioDataFileDescriptor = %d)\n",
+              mAudioDataFileDescriptor);
+        ::close(mAudioDataFileDescriptor);
+        mAudioDataFileDescriptor = -1;
+    }
+
     return AAudioServiceStreamBase::close();
 }
 
@@ -164,6 +171,8 @@
                            : audio_channel_count_from_in_mask(config.channel_mask);
 
     mAudioDataFileDescriptor = mMmapBufferinfo.shared_memory_fd;
+    ALOGV("AAudioServiceStreamMMAP::open LEAK? mAudioDataFileDescriptor = %d\n",
+          mAudioDataFileDescriptor);
     mFramesPerBurst = mMmapBufferinfo.burst_size_frames;
     mCapacityInFrames = mMmapBufferinfo.buffer_size_frames;
     mAudioFormat = AAudioConvert_androidToAAudioDataFormat(config.format);
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.h b/services/oboeservice/AAudioServiceStreamMMAP.h
index a8e63a6..fe75a10 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.h
+++ b/services/oboeservice/AAudioServiceStreamMMAP.h
@@ -127,6 +127,7 @@
     MonotonicCounter                    mFramesWritten;
     MonotonicCounter                    mFramesRead;
     int32_t                             mPreviousFrameCounter = 0;   // from HAL
+    int                                 mAudioDataFileDescriptor = -1;
 
     // Interface to the AudioFlinger MMAP support.
     android::sp<android::MmapStreamInterface> mMmapStream;
diff --git a/services/oboeservice/AAudioServiceStreamShared.cpp b/services/oboeservice/AAudioServiceStreamShared.cpp
index b5d9927..713d1f8 100644
--- a/services/oboeservice/AAudioServiceStreamShared.cpp
+++ b/services/oboeservice/AAudioServiceStreamShared.cpp
@@ -200,6 +200,10 @@
         mEndpointManager.closeEndpoint(endpoint);
         mServiceEndpoint = nullptr;
     }
+    if (mAudioDataQueue != nullptr) {
+        delete mAudioDataQueue;
+        mAudioDataQueue = nullptr;
+    }
     return AAudioServiceStreamBase::close();
 }
 
diff --git a/services/oboeservice/SharedRingBuffer.cpp b/services/oboeservice/SharedRingBuffer.cpp
index 03c160d..6b3fb4c 100644
--- a/services/oboeservice/SharedRingBuffer.cpp
+++ b/services/oboeservice/SharedRingBuffer.cpp
@@ -33,9 +33,13 @@
     if (mSharedMemory != nullptr) {
         delete mFifoBuffer;
         munmap(mSharedMemory, mSharedMemorySizeInBytes);
-        close(mFileDescriptor);
         mSharedMemory = nullptr;
     }
+    if (mFileDescriptor != -1) {
+        ALOGV("SharedRingBuffer: LEAK? close(mFileDescriptor = %d)\n", mFileDescriptor);
+        close(mFileDescriptor);
+        mFileDescriptor = -1;
+    }
 }
 
 aaudio_result_t SharedRingBuffer::allocate(fifo_frames_t   bytesPerFrame,
@@ -46,10 +50,12 @@
     mDataMemorySizeInBytes = bytesPerFrame * capacityInFrames;
     mSharedMemorySizeInBytes = mDataMemorySizeInBytes + (2 * (sizeof(fifo_counter_t)));
     mFileDescriptor = ashmem_create_region("AAudioSharedRingBuffer", mSharedMemorySizeInBytes);
+    ALOGV("SharedRingBuffer::allocate() LEAK? mFileDescriptor = %d\n", mFileDescriptor);
     if (mFileDescriptor < 0) {
         ALOGE("SharedRingBuffer::allocate() ashmem_create_region() failed %d", errno);
         return AAUDIO_ERROR_INTERNAL;
     }
+
     int err = ashmem_set_prot_region(mFileDescriptor, PROT_READ|PROT_WRITE); // TODO error handling?
     if (err < 0) {
         ALOGE("SharedRingBuffer::allocate() ashmem_set_prot_region() failed %d", errno);
@@ -75,9 +81,9 @@
             (fifo_counter_t *) &mSharedMemory[SHARED_RINGBUFFER_WRITE_OFFSET];
     uint8_t *dataAddress = &mSharedMemory[SHARED_RINGBUFFER_DATA_OFFSET];
 
-    mFifoBuffer = new(std::nothrow) FifoBuffer(bytesPerFrame, capacityInFrames,
+    mFifoBuffer = new FifoBuffer(bytesPerFrame, capacityInFrames,
                                  readCounterAddress, writeCounterAddress, dataAddress);
-    return (mFifoBuffer == nullptr) ? AAUDIO_ERROR_NO_MEMORY : AAUDIO_OK;
+    return AAUDIO_OK;
 }
 
 void SharedRingBuffer::fillParcelable(AudioEndpointParcelable &endpointParcelable,