Merge "atrace: add priority inheritance trace point" into pi-dev
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index c9e43fd..a9c5c82 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -1,6 +1,6 @@
 ## Permissions to allow system-wide tracing to the kernel trace buffer.
 ##
-on post-fs
+on late-init
 
 # Allow writing to the kernel trace log.
     chmod 0222 /sys/kernel/debug/tracing/trace_marker
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 5b1e631..6a69844 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -45,6 +45,7 @@
         "libpdx_default_transport",
         "libprotobuf-cpp-lite",
         "libsync",
+        "libtimestats_proto",
         "libui",
         "libutils",
         "libvulkan",
@@ -124,6 +125,7 @@
         "SurfaceFlinger.cpp",
         "SurfaceInterceptor.cpp",
         "SurfaceTracing.cpp",
+        "TimeStats/TimeStats.cpp",
         "Transform.cpp",
     ],
 }
@@ -172,6 +174,7 @@
         "liblayers_proto",
         "liblog",
         "libsurfaceflinger",
+        "libtimestats_proto",
         "libutils",
     ],
     static_libs: [
@@ -213,5 +216,6 @@
 
 subdirs = [
     "layerproto",
+    "TimeStats/timestatsproto",
     "tests",
 ]
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 7fd9d01..4c3844e 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -316,6 +316,9 @@
     nsecs_t desiredPresentTime = mConsumer->getTimestamp();
     mFrameTracker.setDesiredPresentTime(desiredPresentTime);
 
+    const std::string layerName(getName().c_str());
+    mTimeStats.setDesiredTime(layerName, mCurrentFrameNumber, desiredPresentTime);
+
     std::shared_ptr<FenceTime> frameReadyFence = mConsumer->getCurrentFenceTime();
     if (frameReadyFence->isValid()) {
         mFrameTracker.setFrameReadyFence(std::move(frameReadyFence));
@@ -326,12 +329,15 @@
     }
 
     if (presentFence->isValid()) {
+        mTimeStats.setPresentFence(layerName, mCurrentFrameNumber, presentFence);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
     } else {
         // The HWC doesn't support present fences, so use the refresh
         // timestamp instead.
-        mFrameTracker.setActualPresentTime(
-                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY));
+        const nsecs_t actualPresentTime =
+                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY);
+        mTimeStats.setPresentTime(layerName, mCurrentFrameNumber, actualPresentTime);
+        mFrameTracker.setActualPresentTime(actualPresentTime);
     }
 
     mFrameTracker.advanceFrame();
@@ -441,6 +447,7 @@
         // and return early
         if (queuedBuffer) {
             Mutex::Autolock lock(mQueueItemLock);
+            mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             android_atomic_dec(&mQueuedFrames);
         }
@@ -454,6 +461,7 @@
             Mutex::Autolock lock(mQueueItemLock);
             mQueueItems.clear();
             android_atomic_and(0, &mQueuedFrames);
+            mTimeStats.clearLayerRecord(getName().c_str());
         }
 
         // Once we have hit this state, the shadow queue may no longer
@@ -474,10 +482,15 @@
         // Remove any stale buffers that have been dropped during
         // updateTexImage
         while (mQueueItems[0].mFrameNumber != currentFrameNumber) {
+            mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             android_atomic_dec(&mQueuedFrames);
         }
 
+        const std::string layerName(getName().c_str());
+        mTimeStats.setAcquireFence(layerName, currentFrameNumber, mQueueItems[0].mFenceTime);
+        mTimeStats.setLatchTime(layerName, currentFrameNumber, latchTime);
+
         mQueueItems.removeAt(0);
     }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index bbc974d..2802fc7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1532,10 +1532,16 @@
 void Layer::onDisconnect() {
     Mutex::Autolock lock(mFrameEventHistoryMutex);
     mFrameEventHistory.onDisconnect();
+    mTimeStats.onDisconnect(getName().c_str());
 }
 
 void Layer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
                                      FrameEventHistoryDelta* outDelta) {
+    if (newTimestamps) {
+        mTimeStats.setPostTime(getName().c_str(), newTimestamps->frameNumber,
+                               newTimestamps->postedTime);
+    }
+
     Mutex::Autolock lock(mFrameEventHistoryMutex);
     if (newTimestamps) {
         // If there are any unsignaled fences in the aquire timeline at this
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index be3967b..0b15b67 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -41,6 +41,7 @@
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
 #include "SurfaceFlinger.h"
+#include "TimeStats/TimeStats.h"
 #include "Transform.h"
 
 #include <layerproto/LayerProtoHeader.h>
@@ -737,6 +738,8 @@
     FenceTimeline mAcquireTimeline;
     FenceTimeline mReleaseTimeline;
 
+    TimeStats& mTimeStats = TimeStats::getInstance();
+
     // main thread
     int mActiveBufferSlot;
     sp<GraphicBuffer> mActiveBuffer;
diff --git a/services/surfaceflinger/RenderEngine/Description.cpp b/services/surfaceflinger/RenderEngine/Description.cpp
index 0ccdbc4..c218e4d 100644
--- a/services/surfaceflinger/RenderEngine/Description.cpp
+++ b/services/surfaceflinger/RenderEngine/Description.cpp
@@ -52,9 +52,30 @@
 }
 
 void Description::setColorMatrix(const mat4& mtx) {
-    const mat4 identity;
     mColorMatrix = mtx;
-    mColorMatrixEnabled = (mtx != identity);
+}
+
+void Description::setInputTransformMatrix(const mat3& matrix) {
+    mInputTransformMatrix = matrix;
+}
+
+void Description::setOutputTransformMatrix(const mat4& matrix) {
+    mOutputTransformMatrix = matrix;
+}
+
+bool Description::hasInputTransformMatrix() const {
+    const mat3 identity;
+    return mInputTransformMatrix != identity;
+}
+
+bool Description::hasOutputTransformMatrix() const {
+    const mat4 identity;
+    return mOutputTransformMatrix != identity;
+}
+
+bool Description::hasColorMatrix() const {
+    const mat4 identity;
+    return mColorMatrix != identity;
 }
 
 const mat4& Description::getColorMatrix() const {
diff --git a/services/surfaceflinger/RenderEngine/Description.h b/services/surfaceflinger/RenderEngine/Description.h
index b09e3a8..6ebb340 100644
--- a/services/surfaceflinger/RenderEngine/Description.h
+++ b/services/surfaceflinger/RenderEngine/Description.h
@@ -43,6 +43,11 @@
     void setColor(const half4& color);
     void setProjectionMatrix(const mat4& mtx);
     void setColorMatrix(const mat4& mtx);
+    void setInputTransformMatrix(const mat3& matrix);
+    void setOutputTransformMatrix(const mat4& matrix);
+    bool hasInputTransformMatrix() const;
+    bool hasOutputTransformMatrix() const;
+    bool hasColorMatrix() const;
     const mat4& getColorMatrix() const;
 
     void setY410BT2020(bool enable);
@@ -72,11 +77,6 @@
 
     // color used when texturing is disabled or when setting alpha.
     half4 mColor;
-    // projection matrix
-    mat4 mProjectionMatrix;
-
-    bool mColorMatrixEnabled = false;
-    mat4 mColorMatrix;
 
     // true if the sampled pixel values are in Y410/BT2020 rather than RGBA
     bool mY410BT2020 = false;
@@ -86,6 +86,12 @@
     TransferFunction mOutputTransferFunction = TransferFunction::LINEAR;
 
     float mDisplayMaxLuminance;
+
+    // projection matrix
+    mat4 mProjectionMatrix;
+    mat4 mColorMatrix;
+    mat3 mInputTransformMatrix;
+    mat4 mOutputTransformMatrix;
 };
 
 } /* namespace android */
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
index 8e3c837..08cd5b0 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
@@ -131,15 +131,24 @@
     // mColorBlindnessCorrection = M;
 
     if (mPlatformHasWideColor) {
-        // Compute sRGB to DisplayP3 color transform
-        // NOTE: For now, we are limiting wide-color support to
-        // Display-P3 only.
-        mSrgbToDisplayP3 = mat4(
-                ColorSpaceConnector(ColorSpace::sRGB(), ColorSpace::DisplayP3()).getTransform());
+        ColorSpace srgb(ColorSpace::sRGB());
+        ColorSpace displayP3(ColorSpace::DisplayP3());
+        ColorSpace bt2020(ColorSpace::BT2020());
 
-        // Compute BT2020 to DisplayP3 color transform
-        mBt2020ToDisplayP3 = mat4(
-                ColorSpaceConnector(ColorSpace::BT2020(), ColorSpace::DisplayP3()).getTransform());
+        // Compute sRGB to Display P3 transform matrix.
+        // NOTE: For now, we are limiting output wide color space support to
+        // Display-P3 only.
+        mSrgbToDisplayP3 = mat4(ColorSpaceConnector(srgb, displayP3).getTransform());
+
+        // Compute Display P3 to sRGB transform matrix.
+        mDisplayP3ToSrgb = mat4(ColorSpaceConnector(displayP3, srgb).getTransform());
+
+        // no chromatic adaptation needed since all color spaces use D65 for their white points.
+        mSrgbToXyz = srgb.getRGBtoXYZ();
+        mDisplayP3ToXyz = displayP3.getRGBtoXYZ();
+        mBt2020ToXyz = bt2020.getRGBtoXYZ();
+        mXyzToDisplayP3 = mat4(displayP3.getXYZtoRGB());
+        mXyzToBt2020 = mat4(bt2020.getXYZtoRGB());
     }
 }
 
@@ -307,44 +316,96 @@
     glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
                           mesh.getByteStride(), mesh.getPositions());
 
-    // TODO(b/73825729) Refactor this code block to handle BT2020 color space properly.
-    // DISPLAY_P3 is the only supported wide color output
-    if (mPlatformHasWideColor && mOutputDataSpace == Dataspace::DISPLAY_P3) {
+    // By default, DISPLAY_P3 is the only supported wide color output. However,
+    // when HDR content is present, hardware composer may be able to handle
+    // BT2020 data space, in that case, the output data space is set to be
+    // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
+    // to respect this and convert non-HDR content to HDR format.
+    if (mPlatformHasWideColor) {
         Description wideColorState = mState;
-        switch (mDataSpace) {
-            case Dataspace::DISPLAY_P3:
-                // input matches output
-                break;
-            case Dataspace::BT2020_PQ:
-            case Dataspace::BT2020_ITU_PQ:
-                wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3);
-                wideColorState.setInputTransferFunction(Description::TransferFunction::ST2084);
-                wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
-                break;
-            case Dataspace::BT2020_HLG:
-            case Dataspace::BT2020_ITU_HLG:
-                wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3);
-                wideColorState.setInputTransferFunction(Description::TransferFunction::HLG);
-                wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
-                break;
-            default:
-                // treat all other dataspaces as sRGB
-                wideColorState.setColorMatrix(mState.getColorMatrix() * mSrgbToDisplayP3);
-                switch (static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK)) {
-                    case Dataspace::TRANSFER_LINEAR:
-                        wideColorState.setInputTransferFunction(
-                                Description::TransferFunction::LINEAR);
-                        break;
-                    default:
-                        // treat all other transfer functions as sRGB
-                        wideColorState.setInputTransferFunction(
-                                Description::TransferFunction::SRGB);
-                        break;
-                }
-                wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
-                ALOGV("drawMesh: gamut transform applied");
-                break;
+        Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
+        Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
+        Dataspace outputStandard = static_cast<Dataspace>(mOutputDataSpace &
+                                                          Dataspace::STANDARD_MASK);
+        Dataspace outputTransfer = static_cast<Dataspace>(mOutputDataSpace &
+                                                          Dataspace::TRANSFER_MASK);
+        bool needsXYZConversion = needsXYZTransformMatrix();
+
+        if (needsXYZConversion) {
+            // The supported input color spaces are standard RGB, Display P3 and BT2020.
+            switch (inputStandard) {
+                case Dataspace::STANDARD_DCI_P3:
+                    wideColorState.setInputTransformMatrix(mDisplayP3ToXyz);
+                    break;
+                case Dataspace::STANDARD_BT2020:
+                    wideColorState.setInputTransformMatrix(mBt2020ToXyz);
+                    break;
+                default:
+                    wideColorState.setInputTransformMatrix(mSrgbToXyz);
+                    break;
+            }
+
+            // The supported output color spaces are Display P3 and BT2020.
+            switch (outputStandard) {
+                case Dataspace::STANDARD_BT2020:
+                    wideColorState.setOutputTransformMatrix(mXyzToBt2020);
+                    break;
+                default:
+                    wideColorState.setOutputTransformMatrix(mXyzToDisplayP3);
+                    break;
+            }
+        } else if (inputStandard != outputStandard) {
+            // At this point, the input data space and output data space could be both
+            // HDR data spaces, but they match each other, we do nothing in this case.
+            // In addition to the case above, the input data space could be
+            // - scRGB linear
+            // - scRGB non-linear
+            // - sRGB
+            // - Display P3
+            // The output data spaces could be
+            // - sRGB
+            // - Display P3
+            if (outputStandard == Dataspace::STANDARD_BT709) {
+                wideColorState.setOutputTransformMatrix(mDisplayP3ToSrgb);
+            } else if (outputStandard == Dataspace::STANDARD_DCI_P3) {
+                wideColorState.setOutputTransformMatrix(mSrgbToDisplayP3);
+            }
         }
+
+        // we need to convert the RGB value to linear space and convert it back when:
+        // - there is a color matrix that is not an identity matrix, or
+        // - there is an output transform matrix that is not an identity matrix, or
+        // - the input transfer function doesn't match the output transfer function.
+        if (wideColorState.hasColorMatrix() || wideColorState.hasOutputTransformMatrix() ||
+            inputTransfer != outputTransfer) {
+            switch (inputTransfer) {
+                case Dataspace::TRANSFER_ST2084:
+                    wideColorState.setInputTransferFunction(Description::TransferFunction::ST2084);
+                    break;
+                case Dataspace::TRANSFER_HLG:
+                    wideColorState.setInputTransferFunction(Description::TransferFunction::HLG);
+                    break;
+                case Dataspace::TRANSFER_LINEAR:
+                    wideColorState.setInputTransferFunction(Description::TransferFunction::LINEAR);
+                    break;
+                default:
+                    wideColorState.setInputTransferFunction(Description::TransferFunction::SRGB);
+                    break;
+            }
+
+            switch (outputTransfer) {
+                case Dataspace::TRANSFER_ST2084:
+                    wideColorState.setOutputTransferFunction(Description::TransferFunction::ST2084);
+                    break;
+                case Dataspace::TRANSFER_HLG:
+                    wideColorState.setOutputTransferFunction(Description::TransferFunction::HLG);
+                    break;
+                default:
+                    wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
+                    break;
+            }
+        }
+
         ProgramCache::getInstance().useProgram(wideColorState);
 
         glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
@@ -373,6 +434,33 @@
                         dataspaceDetails(static_cast<android_dataspace>(mOutputDataSpace)).c_str());
 }
 
+bool GLES20RenderEngine::isHdrDataSpace(const Dataspace dataSpace) const {
+    const Dataspace standard = static_cast<Dataspace>(dataSpace & Dataspace::STANDARD_MASK);
+    const Dataspace transfer = static_cast<Dataspace>(dataSpace & Dataspace::TRANSFER_MASK);
+    return standard == Dataspace::STANDARD_BT2020 &&
+        (transfer == Dataspace::TRANSFER_ST2084 || transfer == Dataspace::TRANSFER_HLG);
+}
+
+// For convenience, we want to convert the input color space to XYZ color space first,
+// and then convert from XYZ color space to output color space when
+// - SDR and HDR contents are mixed, either SDR content will be converted to HDR or
+//   HDR content will be tone-mapped to SDR; Or,
+// - there are HDR PQ and HLG contents presented at the same time, where we want to convert
+//   HLG content to PQ content.
+// In either case above, we need to operate the Y value in XYZ color space. Thus, when either
+// input data space or output data space is HDR data space, and the input transfer function
+// doesn't match the output transfer function, we would enable an intermediate transfrom to
+// XYZ color space.
+bool GLES20RenderEngine::needsXYZTransformMatrix() const {
+    const bool isInputHdrDataSpace = isHdrDataSpace(mDataSpace);
+    const bool isOutputHdrDataSpace = isHdrDataSpace(mOutputDataSpace);
+    const Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
+    const Dataspace outputTransfer = static_cast<Dataspace>(mOutputDataSpace &
+                                                            Dataspace::TRANSFER_MASK);
+
+    return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
+}
+
 // ---------------------------------------------------------------------------
 } // namespace impl
 } // namespace RE
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
index 7177aad..9acd79b 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
@@ -98,7 +98,18 @@
     // Currently only supporting sRGB, BT2020 and DisplayP3 color spaces
     const bool mPlatformHasWideColor = false;
     mat4 mSrgbToDisplayP3;
-    mat4 mBt2020ToDisplayP3;
+    mat4 mDisplayP3ToSrgb;
+    mat3 mSrgbToXyz;
+    mat3 mBt2020ToXyz;
+    mat3 mDisplayP3ToXyz;
+    mat4 mXyzToDisplayP3;
+    mat4 mXyzToBt2020;
+
+private:
+    // A data space is considered HDR data space if it has BT2020 color space
+    // with PQ or HLG transfer function.
+    bool isHdrDataSpace(const ui::Dataspace dataSpace) const;
+    bool needsXYZTransformMatrix() const;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/RenderEngine/Program.cpp b/services/surfaceflinger/RenderEngine/Program.cpp
index e5261c2..fd2c968 100644
--- a/services/surfaceflinger/RenderEngine/Program.cpp
+++ b/services/surfaceflinger/RenderEngine/Program.cpp
@@ -58,13 +58,13 @@
         mVertexShader = vertexId;
         mFragmentShader = fragmentId;
         mInitialized = true;
-
-        mColorMatrixLoc = glGetUniformLocation(programId, "colorMatrix");
         mProjectionMatrixLoc = glGetUniformLocation(programId, "projection");
         mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
         mSamplerLoc = glGetUniformLocation(programId, "sampler");
         mColorLoc = glGetUniformLocation(programId, "color");
         mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
+        mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
+        mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
 
         // set-up the default values for our uniforms
         glUseProgram(programId);
@@ -134,8 +134,16 @@
         const float color[4] = {desc.mColor.r, desc.mColor.g, desc.mColor.b, desc.mColor.a};
         glUniform4fv(mColorLoc, 1, color);
     }
-    if (mColorMatrixLoc >= 0) {
-        glUniformMatrix4fv(mColorMatrixLoc, 1, GL_FALSE, desc.mColorMatrix.asArray());
+    if (mInputTransformMatrixLoc >= 0) {
+        glUniformMatrix3fv(mInputTransformMatrixLoc, 1, GL_FALSE,
+                           desc.mInputTransformMatrix.asArray());
+    }
+    if (mOutputTransformMatrixLoc >= 0) {
+        // The output transform matrix and color matrix can be combined as one matrix
+        // that is applied right before applying OETF.
+        mat4 outputTransformMatrix = desc.mColorMatrix * desc.mOutputTransformMatrix;
+        glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE,
+                           outputTransformMatrix.asArray());
     }
     if (mDisplayMaxLuminanceLoc >= 0) {
         glUniform1f(mDisplayMaxLuminanceLoc, desc.mDisplayMaxLuminance);
diff --git a/services/surfaceflinger/RenderEngine/Program.h b/services/surfaceflinger/RenderEngine/Program.h
index 55b9cdd..ae796c5 100644
--- a/services/surfaceflinger/RenderEngine/Program.h
+++ b/services/surfaceflinger/RenderEngine/Program.h
@@ -69,9 +69,6 @@
     /* location of the projection matrix uniform */
     GLint mProjectionMatrixLoc;
 
-    /* location of the color matrix uniform */
-    GLint mColorMatrixLoc;
-
     /* location of the texture matrix uniform */
     GLint mTextureMatrixLoc;
 
@@ -83,6 +80,10 @@
 
     /* location of display luminance uniform */
     GLint mDisplayMaxLuminanceLoc;
+
+    /* location of transform matrix */
+    GLint mInputTransformMatrixLoc;
+    GLint mOutputTransformMatrixLoc;
 };
 
 } /* namespace android */
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index 5d5462f..89ee64b 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -125,13 +125,17 @@
                  description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
             .set(Key::OPACITY_MASK,
                  description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
-            .set(Key::COLOR_MATRIX_MASK,
-                 description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON : Key::COLOR_MATRIX_OFF);
+            .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
+                 description.hasInputTransformMatrix() ?
+                     Key::INPUT_TRANSFORM_MATRIX_ON : Key::INPUT_TRANSFORM_MATRIX_OFF)
+            .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
+                 description.hasOutputTransformMatrix() || description.hasColorMatrix() ?
+                     Key::OUTPUT_TRANSFORM_MATRIX_ON : Key::OUTPUT_TRANSFORM_MATRIX_OFF);
 
     needs.set(Key::Y410_BT2020_MASK,
               description.mY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
 
-    if (needs.hasColorMatrix()) {
+    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
         switch (description.mInputTransferFunction) {
             case Description::TransferFunction::LINEAR:
             default:
@@ -441,13 +445,44 @@
             )__SHADER__";
     }
 
-    if (needs.hasColorMatrix()) {
-        fs << "uniform mat4 colorMatrix;";
+    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
         // Currently, only the OOTF of BT2020 PQ needs display maximum luminance.
         if (needs.getInputTF() == Key::INPUT_TF_ST2084) {
             fs << "uniform float displayMaxLuminance";
         }
 
+        if (needs.hasInputTransformMatrix()) {
+            fs << "uniform mat3 inputTransformMatrix;";
+            fs << R"__SHADER__(
+                highp vec3 InputTransform(const highp vec3 color) {
+                    return inputTransformMatrix * color;
+                }
+            )__SHADER__";
+        } else {
+            fs << R"__SHADER__(
+                highp vec3 InputTransform(const highp vec3 color) {
+                    return color;
+                }
+            )__SHADER__";
+        }
+
+        // the transformation from a wider colorspace to a narrower one can
+        // result in >1.0 or <0.0 pixel values
+        if (needs.hasOutputTransformMatrix()) {
+            fs << "uniform mat4 outputTransformMatrix;";
+            fs << R"__SHADER__(
+                highp vec3 OutputTransform(const highp vec3 color) {
+                    return clamp(vec3(outputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
+                }
+            )__SHADER__";
+        } else {
+            fs << R"__SHADER__(
+                highp vec3 OutputTransform(const highp vec3 color) {
+                    return clamp(color, 0.0, 1.0);
+                }
+            )__SHADER__";
+        }
+
         generateEOTF(fs, needs);
         generateOOTF(fs, needs);
         generateOETF(fs, needs);
@@ -476,18 +511,13 @@
         }
     }
 
-    if (needs.hasColorMatrix()) {
+    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
         if (!needs.isOpaque() && needs.isPremultiplied()) {
             // un-premultiply if needed before linearization
             // avoid divide by 0 by adding 0.5/256 to the alpha channel
             fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
         }
-        fs << "vec4 transformed = colorMatrix * vec4(OOTF(EOTF(gl_FragColor.rgb)), 1);";
-        // the transformation from a wider colorspace to a narrower one can
-        // result in >1.0 or <0.0 pixel values
-        fs << "transformed.rgb = clamp(transformed.rgb, 0.0, 1.0);";
-        // We assume the last row is always {0,0,0,1} and we skip the division by w
-        fs << "gl_FragColor.rgb = OETF(transformed.rgb);";
+        fs << "gl_FragColor.rgb = OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))));";
         if (!needs.isOpaque() && needs.isPremultiplied()) {
             // and re-premultiply if needed after gamma correction
             fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.h b/services/surfaceflinger/RenderEngine/ProgramCache.h
index d18163a..e1398eb 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.h
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.h
@@ -72,26 +72,31 @@
             TEXTURE_EXT = 1 << TEXTURE_SHIFT,
             TEXTURE_2D = 2 << TEXTURE_SHIFT,
 
-            COLOR_MATRIX_SHIFT = 5,
-            COLOR_MATRIX_MASK = 1 << COLOR_MATRIX_SHIFT,
-            COLOR_MATRIX_OFF = 0 << COLOR_MATRIX_SHIFT,
-            COLOR_MATRIX_ON = 1 << COLOR_MATRIX_SHIFT,
+            INPUT_TRANSFORM_MATRIX_SHIFT = 5,
+            INPUT_TRANSFORM_MATRIX_MASK = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
+            INPUT_TRANSFORM_MATRIX_OFF = 0 << INPUT_TRANSFORM_MATRIX_SHIFT,
+            INPUT_TRANSFORM_MATRIX_ON = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
 
-            INPUT_TF_SHIFT = 6,
+            OUTPUT_TRANSFORM_MATRIX_SHIFT = 6,
+            OUTPUT_TRANSFORM_MATRIX_MASK = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+            OUTPUT_TRANSFORM_MATRIX_OFF = 0 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+            OUTPUT_TRANSFORM_MATRIX_ON = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+
+            INPUT_TF_SHIFT = 7,
             INPUT_TF_MASK = 3 << INPUT_TF_SHIFT,
             INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT,
             INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT,
             INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT,
             INPUT_TF_HLG = 3 << INPUT_TF_SHIFT,
 
-            OUTPUT_TF_SHIFT = 8,
+            OUTPUT_TF_SHIFT = 9,
             OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT,
             OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT,
             OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT,
             OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT,
             OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT,
 
-            Y410_BT2020_SHIFT = 10,
+            Y410_BT2020_SHIFT = 11,
             Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
             Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
             Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
@@ -110,7 +115,15 @@
         inline bool isPremultiplied() const { return (mKey & BLEND_MASK) == BLEND_PREMULT; }
         inline bool isOpaque() const { return (mKey & OPACITY_MASK) == OPACITY_OPAQUE; }
         inline bool hasAlpha() const { return (mKey & ALPHA_MASK) == ALPHA_LT_ONE; }
-        inline bool hasColorMatrix() const { return (mKey & COLOR_MATRIX_MASK) == COLOR_MATRIX_ON; }
+        inline bool hasInputTransformMatrix() const {
+            return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
+        }
+        inline bool hasOutputTransformMatrix() const {
+            return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
+        }
+        inline bool hasTransformMatrix() const {
+            return hasInputTransformMatrix() || hasOutputTransformMatrix();
+        }
         inline int getInputTF() const { return (mKey & INPUT_TF_MASK); }
         inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); }
         inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f736c8c..cdf7cca 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -244,7 +244,6 @@
         mPrimaryDispSync("PrimaryDispSync"),
         mPrimaryHWVsyncEnabled(false),
         mHWVsyncAvailable(false),
-        mHasColorMatrix(false),
         mHasPoweredOff(false),
         mNumLayers(0),
         mVrFlingerRequestsDisplay(false),
@@ -723,10 +722,13 @@
 }
 
 void SurfaceFlinger::readPersistentProperties() {
+    Mutex::Autolock _l(mStateLock);
+
     char value[PROPERTY_VALUE_MAX];
 
     property_get("persist.sys.sf.color_saturation", value, "1.0");
     mGlobalSaturationFactor = atof(value);
+    updateColorMatrixLocked();
     ALOGV("Saturation is set to %.2f", mGlobalSaturationFactor);
 
     property_get("persist.sys.sf.native_mode", value, "0");
@@ -1470,9 +1472,13 @@
                             Fence::SIGNAL_TIME_PENDING);
             ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
             if (mPropagateBackpressure && frameMissed) {
+                mTimeStats.incrementMissedFrames(true);
                 signalLayerUpdate();
                 break;
             }
+            if (frameMissed) {
+                mTimeStats.incrementMissedFrames(false);
+            }
 
             // Now that we're going to make it to the handleMessageTransaction()
             // call below it's safe to call updateVrFlinger(), which will
@@ -1771,6 +1777,11 @@
         mAnimFrameTracker.advanceFrame();
     }
 
+    mTimeStats.incrementTotalFrames();
+    if (mHadClientComposition) {
+        mTimeStats.incrementClientCompositionFrames();
+    }
+
     if (getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY) &&
             hw->getPowerMode() == HWC_POWER_MODE_OFF) {
         return;
@@ -1859,22 +1870,6 @@
     }
 }
 
-mat4 SurfaceFlinger::computeSaturationMatrix() const {
-    if (mGlobalSaturationFactor == 1.0f) {
-        return mat4();
-    }
-
-    // Rec.709 luma coefficients
-    float3 luminance{0.213f, 0.715f, 0.072f};
-    luminance *= 1.0f - mGlobalSaturationFactor;
-    return mat4(
-        vec4{luminance.r + mGlobalSaturationFactor, luminance.r, luminance.r, 0.0f},
-        vec4{luminance.g, luminance.g + mGlobalSaturationFactor, luminance.g, 0.0f},
-        vec4{luminance.b, luminance.b, luminance.b + mGlobalSaturationFactor, 0.0f},
-        vec4{0.0f, 0.0f, 0.0f, 1.0f}
-    );
-}
-
 // Returns a dataspace that fits all visible layers.  The returned dataspace
 // can only be one of
 //
@@ -2000,9 +1995,6 @@
         }
     }
 
-
-    mat4 colorMatrix = mColorMatrix * computeSaturationMatrix() * mDaltonizer();
-
     // Set the per-frame data
     for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
         auto& displayDevice = mDisplays[displayId];
@@ -2011,9 +2003,9 @@
         if (hwcId < 0) {
             continue;
         }
-        if (colorMatrix != mPreviousColorMatrix) {
-            displayDevice->setColorTransform(colorMatrix);
-            status_t result = getBE().mHwc->setColorTransform(hwcId, colorMatrix);
+        if (mDrawingState.colorMatrixChanged) {
+            displayDevice->setColorTransform(mDrawingState.colorMatrix);
+            status_t result = getBE().mHwc->setColorTransform(hwcId, mDrawingState.colorMatrix);
             ALOGE_IF(result != NO_ERROR, "Failed to set color transform on "
                     "display %zd: %d", displayId, result);
         }
@@ -2046,7 +2038,7 @@
         }
     }
 
-    mPreviousColorMatrix = colorMatrix;
+    mDrawingState.colorMatrixChanged = false;
 
     for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
         auto& displayDevice = mDisplays[displayId];
@@ -2635,6 +2627,9 @@
     mAnimCompositionPending = mAnimTransactionPending;
 
     mDrawingState = mCurrentState;
+    // clear the "changed" flags in current state
+    mCurrentState.colorMatrixChanged = false;
+
     mDrawingState.traverseInZOrder([](Layer* layer) {
         layer->commitChildList();
     });
@@ -2887,9 +2882,8 @@
     mat4 legacySrgbSaturationMatrix = mLegacySrgbSaturationMatrix;
     const bool applyColorMatrix = !hasDeviceComposition && !skipClientColorTransform;
     if (applyColorMatrix) {
-        mat4 colorMatrix = mColorMatrix * computeSaturationMatrix() * mDaltonizer();
-        oldColorMatrix = getRenderEngine().setupColorTransform(colorMatrix);
-        legacySrgbSaturationMatrix = colorMatrix * legacySrgbSaturationMatrix;
+        oldColorMatrix = getRenderEngine().setupColorTransform(mDrawingState.colorMatrix);
+        legacySrgbSaturationMatrix = mDrawingState.colorMatrix * legacySrgbSaturationMatrix;
     }
 
     if (hasClientComposition) {
@@ -3824,12 +3818,6 @@
         size_t index = 0;
         size_t numArgs = args.size();
 
-        if (asProto) {
-            LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
-            result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
-            dumpAll = false;
-        }
-
         if (numArgs) {
             if ((index < numArgs) &&
                     (args[index] == String16("--list"))) {
@@ -3906,10 +3894,21 @@
                 mLayerStats.dump(result);
                 dumpAll = false;
             }
+
+            if ((index < numArgs) && (args[index] == String16("--timestats"))) {
+                index++;
+                mTimeStats.parseArgs(asProto, args, index, result);
+                dumpAll = false;
+            }
         }
 
         if (dumpAll) {
-            dumpAllLocked(args, index, result);
+            if (asProto) {
+                LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
+                result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
+            } else {
+                dumpAllLocked(args, index, result);
+            }
         }
 
         if (locked) {
@@ -4349,6 +4348,30 @@
     return true;
 }
 
+void SurfaceFlinger::updateColorMatrixLocked() {
+    mat4 colorMatrix;
+    if (mGlobalSaturationFactor != 1.0f) {
+        // Rec.709 luma coefficients
+        float3 luminance{0.213f, 0.715f, 0.072f};
+        luminance *= 1.0f - mGlobalSaturationFactor;
+        mat4 saturationMatrix = mat4(
+            vec4{luminance.r + mGlobalSaturationFactor, luminance.r, luminance.r, 0.0f},
+            vec4{luminance.g, luminance.g + mGlobalSaturationFactor, luminance.g, 0.0f},
+            vec4{luminance.b, luminance.b, luminance.b + mGlobalSaturationFactor, 0.0f},
+            vec4{0.0f, 0.0f, 0.0f, 1.0f}
+        );
+        colorMatrix = mClientColorMatrix * saturationMatrix * mDaltonizer();
+    } else {
+        colorMatrix = mClientColorMatrix * mDaltonizer();
+    }
+
+    if (mCurrentState.colorMatrix != colorMatrix) {
+        mCurrentState.colorMatrix = colorMatrix;
+        mCurrentState.colorMatrixChanged = true;
+        setTransactionFlags(eTransactionNeeded);
+    }
+}
+
 status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) {
     switch (code) {
         case CREATE_CONNECTION:
@@ -4483,6 +4506,7 @@
                 return NO_ERROR;
             }
             case 1014: {
+                Mutex::Autolock _l(mStateLock);
                 // daltonize
                 n = data.readInt32();
                 switch (n % 10) {
@@ -4504,33 +4528,33 @@
                 } else {
                     mDaltonizer.setMode(ColorBlindnessMode::Simulation);
                 }
-                invalidateHwcGeometry();
-                repaintEverything();
+
+                updateColorMatrixLocked();
                 return NO_ERROR;
             }
             case 1015: {
+                Mutex::Autolock _l(mStateLock);
                 // apply a color matrix
                 n = data.readInt32();
                 if (n) {
                     // color matrix is sent as a column-major mat4 matrix
                     for (size_t i = 0 ; i < 4; i++) {
                         for (size_t j = 0; j < 4; j++) {
-                            mColorMatrix[i][j] = data.readFloat();
+                            mClientColorMatrix[i][j] = data.readFloat();
                         }
                     }
                 } else {
-                    mColorMatrix = mat4();
+                    mClientColorMatrix = mat4();
                 }
 
                 // Check that supplied matrix's last row is {0,0,0,1} so we can avoid
                 // the division by w in the fragment shader
-                float4 lastRow(transpose(mColorMatrix)[3]);
+                float4 lastRow(transpose(mClientColorMatrix)[3]);
                 if (any(greaterThan(abs(lastRow - float4{0, 0, 0, 1}), float4{1e-4f}))) {
                     ALOGE("The color transform's last row must be (0, 0, 0, 1)");
                 }
 
-                invalidateHwcGeometry();
-                repaintEverything();
+                updateColorMatrixLocked();
                 return NO_ERROR;
             }
             // This is an experimental interface
@@ -4573,10 +4597,10 @@
                 return NO_ERROR;
             }
             case 1022: { // Set saturation boost
+                Mutex::Autolock _l(mStateLock);
                 mGlobalSaturationFactor = std::max(0.0f, std::min(data.readFloat(), 2.0f));
 
-                invalidateHwcGeometry();
-                repaintEverything();
+                updateColorMatrixLocked();
                 return NO_ERROR;
             }
             case 1023: { // Set native mode
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 33706da..54cf63c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,6 +63,7 @@
 #include "SurfaceInterceptor.h"
 #include "SurfaceTracing.h"
 #include "StartPropertySetThread.h"
+#include "TimeStats/TimeStats.h"
 #include "VSyncModulator.h"
 
 #include "DisplayHardware/HWC2.h"
@@ -372,6 +373,10 @@
             // always uses the Drawing StateSet.
             layersSortedByZ = other.layersSortedByZ;
             displays = other.displays;
+            colorMatrixChanged = other.colorMatrixChanged;
+            if (colorMatrixChanged) {
+                colorMatrix = other.colorMatrix;
+            }
             return *this;
         }
 
@@ -379,6 +384,9 @@
         LayerVector layersSortedByZ;
         DefaultKeyedVector< wp<IBinder>, DisplayDeviceState> displays;
 
+        bool colorMatrixChanged = true;
+        mat4 colorMatrix;
+
         void traverseInZOrder(const LayerVector::Visitor& visitor) const;
         void traverseInReverseZOrder(const LayerVector::Visitor& visitor) const;
     };
@@ -652,8 +660,6 @@
                        ui::ColorMode* outMode,
                        ui::Dataspace* outDataSpace) const;
 
-    mat4 computeSaturationMatrix() const;
-
     void setUpHWComposer();
     void doComposition();
     void doDebugFlashRegions();
@@ -740,6 +746,8 @@
     // Check to see if we should handoff to vr flinger.
     void updateVrFlinger();
 
+    void updateColorMatrixLocked();
+
     /* ------------------------------------------------------------------------
      * Attributes
      */
@@ -753,6 +761,11 @@
     bool mAnimTransactionPending;
     SortedVector< sp<Layer> > mLayersPendingRemoval;
 
+    // global color transform states
+    Daltonizer mDaltonizer;
+    float mGlobalSaturationFactor = 1.0f;
+    mat4 mClientColorMatrix;
+
     // Can't be unordered_set because wp<> isn't hashable
     std::set<wp<IBinder>> mGraphicBufferProducerList;
     size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS;
@@ -815,6 +828,7 @@
             std::make_unique<impl::SurfaceInterceptor>(this);
     SurfaceTracing mTracing;
     LayerStats mLayerStats;
+    TimeStats& mTimeStats = TimeStats::getInstance();
     bool mUseHwcVirtualDisplays = false;
 
     // Restrict layers to use two buffers in their bufferqueues.
@@ -842,12 +856,6 @@
 
     bool mInjectVSyncs;
 
-    Daltonizer mDaltonizer;
-
-    mat4 mPreviousColorMatrix;
-    mat4 mColorMatrix;
-    bool mHasColorMatrix;
-
     // Static screen stats
     bool mHasPoweredOff;
 
@@ -865,8 +873,6 @@
     DisplayColorSetting mDisplayColorSetting = DisplayColorSetting::MANAGED;
     // Applied on sRGB layers when the render intent is non-colorimetric.
     mat4 mLegacySrgbSaturationMatrix;
-    // Applied globally.
-    float mGlobalSaturationFactor = 1.0f;
     bool mBuiltinDisplaySupportsEnhance = false;
 
     using CreateBufferQueueFunction =
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
new file mode 100644
index 0000000..5f2dd32
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2018 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.
+ */
+#undef LOG_TAG
+#define LOG_TAG "TimeStats"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "TimeStats.h"
+
+#include <android-base/stringprintf.h>
+
+#include <log/log.h>
+
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <regex>
+
+namespace android {
+
+TimeStats& TimeStats::getInstance() {
+    static std::unique_ptr<TimeStats> sInstance;
+    static std::once_flag sOnceFlag;
+
+    std::call_once(sOnceFlag, [] { sInstance.reset(new TimeStats); });
+    return *sInstance.get();
+}
+
+void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, size_t& index,
+                          String8& result) {
+    ATRACE_CALL();
+
+    if (args.size() > index + 10) {
+        ALOGD("Invalid args count");
+        return;
+    }
+
+    std::unordered_map<std::string, int32_t> argsMap;
+    while (index < args.size()) {
+        argsMap[std::string(String8(args[index]).c_str())] = index;
+        ++index;
+    }
+
+    if (argsMap.count("-disable")) {
+        disable();
+    }
+
+    if (argsMap.count("-dump")) {
+        int64_t maxLayers = 0;
+        auto iter = argsMap.find("-maxlayers");
+        if (iter != argsMap.end() && iter->second + 1 < static_cast<int32_t>(args.size())) {
+            maxLayers = strtol(String8(args[iter->second + 1]).c_str(), nullptr, 10);
+            maxLayers = std::clamp(maxLayers, int64_t(0), int64_t(UINT32_MAX));
+        }
+
+        dump(asProto, static_cast<uint32_t>(maxLayers), result);
+    }
+
+    if (argsMap.count("-clear")) {
+        clear();
+    }
+
+    if (argsMap.count("-enable")) {
+        enable();
+    }
+}
+
+void TimeStats::incrementTotalFrames() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    timeStats.totalFrames++;
+}
+
+void TimeStats::incrementMissedFrames(bool propagateBackpressure) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (propagateBackpressure) {
+        timeStats.totalFrames--;
+    }
+    timeStats.missedFrames++;
+}
+
+void TimeStats::incrementClientCompositionFrames() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    timeStats.clientCompositionFrames++;
+}
+
+bool TimeStats::recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord) {
+    if (!timeRecord->ready) {
+        ALOGV("[%s]-[%" PRIu64 "]-presentFence is still not received", layerName.c_str(),
+              timeRecord->frameNumber);
+        return false;
+    }
+
+    if (timeRecord->acquireFence != nullptr) {
+        if (timeRecord->acquireFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+            return false;
+        }
+        if (timeRecord->acquireFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+            timeRecord->acquireTime = timeRecord->acquireFence->getSignalTime();
+            timeRecord->acquireFence = nullptr;
+        } else {
+            ALOGV("[%s]-[%" PRIu64 "]-acquireFence signal time is invalid", layerName.c_str(),
+                  timeRecord->frameNumber);
+        }
+    }
+
+    if (timeRecord->presentFence != nullptr) {
+        if (timeRecord->presentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+            return false;
+        }
+        if (timeRecord->presentFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+            timeRecord->presentTime = timeRecord->presentFence->getSignalTime();
+            timeRecord->presentFence = nullptr;
+        } else {
+            ALOGV("[%s]-[%" PRIu64 "]-presentFence signal time invalid", layerName.c_str(),
+                  timeRecord->frameNumber);
+        }
+    }
+
+    return true;
+}
+
+static int32_t msBetween(nsecs_t start, nsecs_t end) {
+    int64_t delta = (end - start) / 1000000;
+    delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
+    return static_cast<int32_t>(delta);
+}
+
+void TimeStats::flushAvailableRecordsToStatsLocked(const std::string& layerName) {
+    ATRACE_CALL();
+
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
+    std::vector<TimeRecord>& timeRecords = layerRecord.timeRecords;
+    while (!timeRecords.empty()) {
+        if (!recordReadyLocked(layerName, &timeRecords[0])) break;
+        ALOGV("[%s]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerName.c_str(),
+              timeRecords[0].frameNumber, timeRecords[0].presentTime);
+
+        if (prevTimeRecord.ready) {
+            if (!timeStats.stats.count(layerName)) {
+                timeStats.stats[layerName].layerName = layerName;
+                timeStats.stats[layerName].statsStart = static_cast<int64_t>(std::time(0));
+            }
+            TimeStatsHelper::TimeStatsLayer& timeStatsLayer = timeStats.stats[layerName];
+            timeStatsLayer.totalFrames++;
+
+            const int32_t postToPresentMs =
+                    msBetween(timeRecords[0].postTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-post2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, postToPresentMs);
+            timeStatsLayer.deltas["post2present"].insert(postToPresentMs);
+
+            const int32_t acquireToPresentMs =
+                    msBetween(timeRecords[0].acquireTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-acquire2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, acquireToPresentMs);
+            timeStatsLayer.deltas["acquire2present"].insert(acquireToPresentMs);
+
+            const int32_t latchToPresentMs =
+                    msBetween(timeRecords[0].latchTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-latch2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, latchToPresentMs);
+            timeStatsLayer.deltas["latch2present"].insert(latchToPresentMs);
+
+            const int32_t desiredToPresentMs =
+                    msBetween(timeRecords[0].desiredTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-desired2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, desiredToPresentMs);
+            timeStatsLayer.deltas["desired2present"].insert(desiredToPresentMs);
+
+            const int32_t presentToPresentMs =
+                    msBetween(prevTimeRecord.presentTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-present2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, presentToPresentMs);
+            timeStatsLayer.deltas["present2present"].insert(presentToPresentMs);
+
+            timeStats.stats[layerName].statsEnd = static_cast<int64_t>(std::time(0));
+        }
+        prevTimeRecord = timeRecords[0];
+        // TODO(zzyiwei): change timeRecords to use std::deque
+        timeRecords.erase(timeRecords.begin());
+        layerRecord.waitData--;
+    }
+}
+
+static bool layerNameIsValid(const std::string& layerName) {
+    // This regular expression captures the following layer names for instance:
+    // 1) StatusBat#0
+    // 2) NavigationBar#1
+    // 3) com.*#0
+    // 4) SurfaceView - com.*#0
+    // Using [-\\s\t]+ for the conjunction part between SurfaceView and com.* is
+    // a bit more robust in case there's a slight change.
+    // The layer name would only consist of . / $ _ 0-9 a-z A-Z in most cases.
+    std::regex re("(((SurfaceView[-\\s\\t]+)?com\\.[./$\\w]+)|((Status|Navigation)Bar))#\\d+");
+    return std::regex_match(layerName.begin(), layerName.end(), re);
+}
+
+void TimeStats::setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PostTime[%" PRId64 "]", layerName.c_str(), frameNumber, postTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName) && !layerNameIsValid(layerName)) {
+        return;
+    }
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    if (layerRecord.timeRecords.size() == MAX_NUM_TIME_RECORDS) {
+        ALOGV("[%s]-timeRecords is already at its maximum size[%zu]", layerName.c_str(),
+              MAX_NUM_TIME_RECORDS);
+        // TODO(zzyiwei): if this happens, there must be a present fence missing
+        // or waitData is not in the correct position. Need to think out a
+        // reasonable way to recover from this state.
+        return;
+    }
+    // For most media content, the acquireFence is invalid because the buffer is
+    // ready at the queueBuffer stage. In this case, acquireTime should be given
+    // a default value as postTime.
+    TimeRecord timeRecord = {
+            .frameNumber = frameNumber,
+            .postTime = postTime,
+            .acquireTime = postTime,
+    };
+    layerRecord.timeRecords.push_back(timeRecord);
+    if (layerRecord.waitData < 0 ||
+        layerRecord.waitData >= static_cast<int32_t>(layerRecord.timeRecords.size()))
+        layerRecord.waitData = layerRecord.timeRecords.size() - 1;
+}
+
+void TimeStats::setLatchTime(const std::string& layerName, uint64_t frameNumber,
+                             nsecs_t latchTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-LatchTime[%" PRId64 "]", layerName.c_str(), frameNumber, latchTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.latchTime = latchTime;
+    }
+}
+
+void TimeStats::setDesiredTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t desiredTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-DesiredTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          desiredTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.desiredTime = desiredTime;
+    }
+}
+
+void TimeStats::setAcquireTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t acquireTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-AcquireTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          acquireTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.acquireTime = acquireTime;
+    }
+}
+
+void TimeStats::setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+                                const std::shared_ptr<FenceTime>& acquireFence) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-AcquireFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          acquireFence->getSignalTime());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.acquireFence = acquireFence;
+    }
+}
+
+void TimeStats::setPresentTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t presentTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PresentTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          presentTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.presentTime = presentTime;
+        timeRecord.ready = true;
+        layerRecord.waitData++;
+    }
+
+    flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::setPresentFence(const std::string& layerName, uint64_t frameNumber,
+                                const std::shared_ptr<FenceTime>& presentFence) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PresentFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          presentFence->getSignalTime());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.presentFence = presentFence;
+        timeRecord.ready = true;
+        layerRecord.waitData++;
+    }
+
+    flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::onDisconnect(const std::string& layerName) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-onDisconnect", layerName.c_str());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    flushAvailableRecordsToStatsLocked(layerName);
+    timeStatsTracker.erase(layerName);
+}
+
+void TimeStats::clearLayerRecord(const std::string& layerName) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-clearLayerRecord", layerName.c_str());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    layerRecord.timeRecords.clear();
+    layerRecord.prevTimeRecord.ready = false;
+    layerRecord.waitData = -1;
+}
+
+void TimeStats::removeTimeRecord(const std::string& layerName, uint64_t frameNumber) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-removeTimeRecord", layerName.c_str(), frameNumber);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    size_t removeAt = 0;
+    for (const TimeRecord& record : layerRecord.timeRecords) {
+        if (record.frameNumber == frameNumber) break;
+        removeAt++;
+    }
+    if (removeAt == layerRecord.timeRecords.size()) return;
+    layerRecord.timeRecords.erase(layerRecord.timeRecords.begin() + removeAt);
+    if (layerRecord.waitData > static_cast<int32_t>(removeAt)) {
+        --layerRecord.waitData;
+    }
+}
+
+void TimeStats::enable() {
+    if (mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Enabled");
+    mEnabled.store(true);
+    timeStats.statsStart = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::disable() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Disabled");
+    mEnabled.store(false);
+    timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::clear() {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Cleared");
+    timeStats.dumpStats.clear();
+    timeStats.stats.clear();
+    timeStats.statsStart = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
+    timeStats.statsEnd = 0;
+    timeStats.totalFrames = 0;
+    timeStats.missedFrames = 0;
+    timeStats.clientCompositionFrames = 0;
+}
+
+bool TimeStats::isEnabled() {
+    return mEnabled.load();
+}
+
+void TimeStats::dump(bool asProto, uint32_t maxLayers, String8& result) {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (timeStats.statsStart == 0) {
+        return;
+    }
+
+    timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+
+    // TODO(zzyiwei): refactor dumpStats into TimeStatsHelper
+    timeStats.dumpStats.clear();
+    for (auto& ele : timeStats.stats) {
+        timeStats.dumpStats.push_back(&ele.second);
+    }
+
+    std::sort(timeStats.dumpStats.begin(), timeStats.dumpStats.end(),
+              [](TimeStatsHelper::TimeStatsLayer* const& l,
+                 TimeStatsHelper::TimeStatsLayer* const& r) {
+                  return l->totalFrames > r->totalFrames;
+              });
+
+    if (maxLayers != 0 && maxLayers < timeStats.dumpStats.size()) {
+        timeStats.dumpStats.resize(maxLayers);
+    }
+
+    if (asProto) {
+        dumpAsProtoLocked(result);
+    } else {
+        dumpAsTextLocked(result);
+    }
+}
+
+void TimeStats::dumpAsTextLocked(String8& result) {
+    ALOGD("Dumping TimeStats as text");
+    result.append(timeStats.toString().c_str());
+    result.append("\n");
+}
+
+void TimeStats::dumpAsProtoLocked(String8& result) {
+    ALOGD("Dumping TimeStats as proto");
+    SFTimeStatsGlobalProto timeStatsProto = timeStats.toProto();
+    result.append(timeStatsProto.SerializeAsString().c_str(), timeStatsProto.ByteSize());
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
new file mode 100644
index 0000000..2410265
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsHelper.h>
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <ui/FenceTime.h>
+
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+class String8;
+
+class TimeStats {
+    // TODO(zzyiwei): Bound the timeStatsTracker with weighted LRU
+    // static const size_t MAX_NUM_LAYER_RECORDS = 200;
+    static const size_t MAX_NUM_TIME_RECORDS = 64;
+
+    struct TimeRecord {
+        bool ready = false;
+        uint64_t frameNumber = 0;
+        nsecs_t postTime = 0;
+        nsecs_t latchTime = 0;
+        nsecs_t acquireTime = 0;
+        nsecs_t desiredTime = 0;
+        nsecs_t presentTime = 0;
+        std::shared_ptr<FenceTime> acquireFence;
+        std::shared_ptr<FenceTime> presentFence;
+    };
+
+    struct LayerRecord {
+        // This is the index in timeRecords, at which the timestamps for that
+        // specific frame are still not fully received. This is not waiting for
+        // fences to signal, but rather waiting to receive those fences/timestamps.
+        int32_t waitData = -1;
+        TimeRecord prevTimeRecord;
+        std::vector<TimeRecord> timeRecords;
+    };
+
+public:
+    static TimeStats& getInstance();
+    void parseArgs(bool asProto, const Vector<String16>& args, size_t& index, String8& result);
+    void incrementTotalFrames();
+    void incrementMissedFrames(bool propagateBackpressure);
+    void incrementClientCompositionFrames();
+
+    void setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime);
+    void setLatchTime(const std::string& layerName, uint64_t frameNumber, nsecs_t latchTime);
+    void setDesiredTime(const std::string& layerName, uint64_t frameNumber, nsecs_t desiredTime);
+    void setAcquireTime(const std::string& layerName, uint64_t frameNumber, nsecs_t acquireTime);
+    void setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+                         const std::shared_ptr<FenceTime>& acquireFence);
+    void setPresentTime(const std::string& layerName, uint64_t frameNumber, nsecs_t presentTime);
+    void setPresentFence(const std::string& layerName, uint64_t frameNumber,
+                         const std::shared_ptr<FenceTime>& presentFence);
+    void onDisconnect(const std::string& layerName);
+    void clearLayerRecord(const std::string& layerName);
+    void removeTimeRecord(const std::string& layerName, uint64_t frameNumber);
+
+private:
+    TimeStats() = default;
+
+    bool recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord);
+    void flushAvailableRecordsToStatsLocked(const std::string& layerName);
+
+    void enable();
+    void disable();
+    void clear();
+    bool isEnabled();
+    void dump(bool asProto, uint32_t maxLayer, String8& result);
+    void dumpAsTextLocked(String8& result);
+    void dumpAsProtoLocked(String8& result);
+
+    std::atomic<bool> mEnabled = false;
+    std::mutex mMutex;
+    TimeStatsHelper::TimeStatsGlobal timeStats;
+    std::unordered_map<std::string, LayerRecord> timeStatsTracker;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
new file mode 100644
index 0000000..66aa719
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -0,0 +1,55 @@
+cc_library_shared {
+    name: "libtimestats_proto",
+    vendor_available: true,
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "TimeStatsHelper.cpp",
+        "timestats.proto",
+    ],
+
+    shared_libs: [
+        "android.hardware.graphics.common@1.1",
+        "libui",
+        "libprotobuf-cpp-lite",
+        "libbase",
+        "liblog",
+    ],
+
+    proto: {
+        export_proto_headers: true,
+    },
+
+    cppflags: [
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wno-format",
+        "-Wno-c++98-compat-pedantic",
+        "-Wno-float-conversion",
+        "-Wno-disabled-macro-expansion",
+        "-Wno-float-equal",
+        "-Wno-sign-conversion",
+        "-Wno-padded",
+        "-Wno-old-style-cast",
+        "-Wno-undef",
+    ],
+
+}
+
+java_library_static {
+    name: "timestatsprotosnano",
+    host_supported: true,
+    proto: {
+        type: "nano",
+    },
+    srcs: ["*.proto"],
+    no_framework_libs: true,
+    target: {
+        android: {
+            jarjar_rules: "jarjar-rules.txt",
+        },
+        host: {
+            static_libs: ["libprotobuf-java-nano"],
+        },
+    },
+}
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
new file mode 100644
index 0000000..3e5007c
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+#include <android-base/stringprintf.h>
+#include <timestatsproto/TimeStatsHelper.h>
+
+#include <array>
+#include <regex>
+
+#define HISTOGRAM_SIZE 85
+
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
+namespace android {
+namespace surfaceflinger {
+
+// Time buckets for histogram, the calculated time deltas will be lower bounded
+// to the buckets in this array.
+static const std::array<int32_t, HISTOGRAM_SIZE> histogramConfig =
+        {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,
+         17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,
+         34,  36,  38,  40,  42,  44,  46,  48,  50,  54,  58,  62,  66,  70,  74,  78,  82,
+         86,  90,  94,  98,  102, 106, 110, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150,
+         200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000};
+
+void TimeStatsHelper::Histogram::insert(int32_t delta) {
+    if (delta < 0) return;
+    // std::lower_bound won't work on out of range values
+    if (delta > histogramConfig[HISTOGRAM_SIZE - 1]) {
+        hist[histogramConfig[HISTOGRAM_SIZE - 1]]++;
+        return;
+    }
+    auto iter = std::lower_bound(histogramConfig.begin(), histogramConfig.end(), delta);
+    hist[*iter]++;
+}
+
+float TimeStatsHelper::Histogram::averageTime() {
+    int64_t ret = 0;
+    int64_t count = 0;
+    for (auto ele : hist) {
+        count += ele.second;
+        ret += ele.first * ele.second;
+    }
+    return static_cast<float>(ret) / count;
+}
+
+std::string TimeStatsHelper::Histogram::toString() {
+    std::string result;
+    for (int32_t i = 0; i < HISTOGRAM_SIZE; ++i) {
+        int32_t bucket = histogramConfig[i];
+        int32_t count = (hist.count(bucket) == 0) ? 0 : hist[bucket];
+        StringAppendF(&result, "%dms=%d ", bucket, count);
+    }
+    result.back() = '\n';
+    return result;
+}
+
+static std::string getPackageName(const std::string& layerName) {
+    // This regular expression captures the following for instance:
+    // StatusBar in StatusBar#0
+    // com.appname in com.appname/com.appname.activity#0
+    // com.appname in SurfaceView - com.appname/com.appname.activity#0
+    const std::regex re("(?:SurfaceView[-\\s\\t]+)?([^/]+).*#\\d+");
+    std::smatch match;
+    if (std::regex_match(layerName.begin(), layerName.end(), match, re)) {
+        // There must be a match for group 1 otherwise the whole string is not
+        // matched and the above will return false
+        return match[1];
+    }
+    return "";
+}
+
+std::string TimeStatsHelper::TimeStatsLayer::toString() {
+    std::string result = "";
+    StringAppendF(&result, "layerName = %s\n", layerName.c_str());
+    packageName = getPackageName(layerName);
+    StringAppendF(&result, "packageName = %s\n", packageName.c_str());
+    StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+    StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+    StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+    if (deltas.find("present2present") != deltas.end()) {
+        StringAppendF(&result, "averageFPS = %.3f\n",
+                      1000.0 / deltas["present2present"].averageTime());
+    }
+    for (auto ele : deltas) {
+        StringAppendF(&result, "%s histogram is as below:\n", ele.first.c_str());
+        StringAppendF(&result, "%s", ele.second.toString().c_str());
+    }
+
+    return result;
+}
+
+std::string TimeStatsHelper::TimeStatsGlobal::toString() {
+    std::string result = "SurfaceFlinger TimeStats:\n";
+    StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+    StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+    StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+    StringAppendF(&result, "missedFrames= %d\n", missedFrames);
+    StringAppendF(&result, "clientCompositionFrames= %d\n", clientCompositionFrames);
+    StringAppendF(&result, "TimeStats for each layer is as below:\n");
+    for (auto ele : dumpStats) {
+        StringAppendF(&result, "%s", ele->toString().c_str());
+    }
+
+    return result;
+}
+
+SFTimeStatsLayerProto TimeStatsHelper::TimeStatsLayer::toProto() {
+    SFTimeStatsLayerProto layerProto;
+    layerProto.set_layer_name(layerName);
+    packageName = getPackageName(layerName);
+    layerProto.set_package_name(packageName);
+    layerProto.set_stats_start(statsStart);
+    layerProto.set_stats_end(statsEnd);
+    layerProto.set_total_frames(totalFrames);
+    for (auto ele : deltas) {
+        SFTimeStatsDeltaProto* deltaProto = layerProto.add_deltas();
+        deltaProto->set_delta_name(ele.first);
+        for (auto histEle : ele.second.hist) {
+            SFTimeStatsHistogramBucketProto* histProto = deltaProto->add_histograms();
+            histProto->set_render_millis(histEle.first);
+            histProto->set_frame_count(histEle.second);
+        }
+    }
+    return layerProto;
+}
+
+SFTimeStatsGlobalProto TimeStatsHelper::TimeStatsGlobal::toProto() {
+    SFTimeStatsGlobalProto globalProto;
+    globalProto.set_stats_start(statsStart);
+    globalProto.set_stats_end(statsEnd);
+    globalProto.set_total_frames(totalFrames);
+    globalProto.set_missed_frames(missedFrames);
+    globalProto.set_client_composition_frames(clientCompositionFrames);
+    for (auto ele : dumpStats) {
+        SFTimeStatsLayerProto* layerProto = globalProto.add_stats();
+        layerProto->CopyFrom(ele->toProto());
+    }
+    return globalProto;
+}
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
new file mode 100644
index 0000000..c876f21
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <math/vec4.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace surfaceflinger {
+
+class TimeStatsHelper {
+public:
+    class Histogram {
+    public:
+        // Key is the delta time between timestamps
+        // Value is the number of appearances of that delta
+        std::unordered_map<int32_t, int32_t> hist;
+
+        void insert(int32_t delta);
+        float averageTime();
+        std::string toString();
+    };
+
+    class TimeStatsLayer {
+    public:
+        std::string layerName;
+        std::string packageName;
+        int64_t statsStart = 0;
+        int64_t statsEnd = 0;
+        int32_t totalFrames = 0;
+        std::unordered_map<std::string, Histogram> deltas;
+
+        std::string toString();
+        SFTimeStatsLayerProto toProto();
+    };
+
+    class TimeStatsGlobal {
+    public:
+        int64_t statsStart = 0;
+        int64_t statsEnd = 0;
+        int32_t totalFrames = 0;
+        int32_t missedFrames = 0;
+        int32_t clientCompositionFrames = 0;
+        std::unordered_map<std::string, TimeStatsLayer> stats;
+        std::vector<TimeStatsLayer*> dumpStats;
+
+        std::string toString();
+        SFTimeStatsGlobalProto toProto();
+    };
+};
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
new file mode 100644
index 0000000..fe0d150
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Projectlayerproto/LayerProtoHeader.h
+ *
+ * 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.
+ */
+
+// pragma is used here to disable the warnings emitted from the protobuf
+// headers. By adding #pragma before including layer.pb.h, it supresses
+// protobuf warnings, but allows the rest of the files to continuing using
+// the current flags.
+// This file should be included instead of directly including layer.b.h
+#pragma GCC system_header
+#include <timestats.pb.h>
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
new file mode 100644
index 0000000..40043a8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
new file mode 100644
index 0000000..a8f6fa8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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.
+ */
+
+syntax = "proto2";
+
+package android.surfaceflinger;
+
+option optimize_for = LITE_RUNTIME;
+
+// frameworks/base/core/proto/android/service/sftimestats.proto is based on
+// this proto. Please only make valid protobuf changes to these messages, and
+// keep the other file in sync with this one.
+
+message SFTimeStatsGlobalProto {
+  // The start & end timestamps in UTC as
+  // milliseconds since January 1, 1970
+  optional int64 stats_start = 1;
+  optional int64 stats_end = 2;
+  // Total frames
+  optional int32 total_frames = 3;
+  // Total missed frames of SurfaceFlinger.
+  optional int32 missed_frames = 4;
+  // Total frames fallback to client composition.
+  optional int32 client_composition_frames = 5;
+
+  repeated SFTimeStatsLayerProto stats = 6;
+}
+
+message SFTimeStatsLayerProto {
+  // The layer name
+  optional string layer_name = 1;
+  // The package name
+  optional string package_name = 2;
+  // The start & end timestamps in UTC as
+  // milliseconds since January 1, 1970
+  optional int64 stats_start = 3;
+  optional int64 stats_end = 4;
+  // Distinct frame count.
+  optional int32 total_frames = 5;
+
+  repeated SFTimeStatsDeltaProto deltas = 6;
+}
+
+message SFTimeStatsDeltaProto {
+  // Name of the time interval
+  optional string delta_name = 1;
+  // Histogram of the delta time
+  repeated SFTimeStatsHistogramBucketProto histograms = 2;
+}
+
+message SFTimeStatsHistogramBucketProto {
+  // Lower bound of render time in milliseconds.
+  optional int32 render_millis = 1;
+  // Number of frames in the bucket.
+  optional int32 frame_count = 2;
+}
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 7523399..322e8a0 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -36,6 +36,7 @@
         "liblayers_proto",
         "liblog",
         "libprotobuf-cpp-full",
+        "libtimestats_proto",
         "libui",
         "libutils",
     ]
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 00bc621..520df2d 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -25,6 +25,7 @@
         "liblog",
         "libnativewindow",
         "libsync",
+        "libtimestats_proto",
         "libui",
         "libutils",
     ],