Merge "Fix BufferHubProducer log spam" into pi-dev
diff --git a/headers/media_plugin/media/openmax/OMX_Audio.h b/headers/media_plugin/media/openmax/OMX_Audio.h
index 9c0296b..f8a36bd 100644
--- a/headers/media_plugin/media/openmax/OMX_Audio.h
+++ b/headers/media_plugin/media/openmax/OMX_Audio.h
@@ -263,6 +263,7 @@
   OMX_AUDIO_AACObjectLD = 23,       /**< AAC Low Delay object (Error Resilient) */
   OMX_AUDIO_AACObjectHE_PS = 29,    /**< AAC High Efficiency with Parametric Stereo coding (HE-AAC v2, object type PS) */
   OMX_AUDIO_AACObjectELD = 39,      /** AAC Enhanced Low Delay. NOTE: Pending Khronos standardization **/
+  OMX_AUDIO_AACObjectXHE = 42,      /** extended High Efficiency AAC. NOTE: Pending Khronos standardization */
   OMX_AUDIO_AACObjectKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
   OMX_AUDIO_AACObjectVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
   OMX_AUDIO_AACObjectMax = 0x7FFFFFFF
diff --git a/headers/media_plugin/media/openmax/OMX_AudioExt.h b/headers/media_plugin/media/openmax/OMX_AudioExt.h
index 05c2232..8409553 100644
--- a/headers/media_plugin/media/openmax/OMX_AudioExt.h
+++ b/headers/media_plugin/media/openmax/OMX_AudioExt.h
@@ -82,6 +82,7 @@
                                    limit the audio signal. Use 0 to let encoder decide */
 } OMX_AUDIO_PARAM_ANDROID_OPUSTYPE;
 
+/** deprecated. use OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE */
 typedef struct OMX_AUDIO_PARAM_ANDROID_AACPRESENTATIONTYPE {
     OMX_U32 nSize;            /**< size of the structure in bytes */
     OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
@@ -94,6 +95,19 @@
     OMX_S32 nPCMLimiterEnable;     /**< Signal level limiting, 0 for disable, 1 for enable, -1 if unspecified */
 } OMX_AUDIO_PARAM_ANDROID_AACPRESENTATIONTYPE;
 
+typedef struct OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE {
+    OMX_U32 nSize;            /**< size of the structure in bytes */
+    OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
+    OMX_S32 nMaxOutputChannels;    /**< Maximum channel count to be output, -1 if unspecified, 0 if downmixing disabled */
+    OMX_S32 nDrcCut;               /**< The DRC attenuation factor, between 0 and 127, -1 if unspecified */
+    OMX_S32 nDrcBoost;             /**< The DRC amplification factor, between 0 and 127, -1 if unspecified */
+    OMX_S32 nHeavyCompression;     /**< 0 for light compression, 1 for heavy compression, -1 if unspecified */
+    OMX_S32 nTargetReferenceLevel; /**< Target reference level, between 0 and 127, -1 if unspecified */
+    OMX_S32 nEncodedTargetLevel;   /**< Target reference level assumed at the encoder, between 0 and 127, -1 if unspecified */
+    OMX_S32 nPCMLimiterEnable;     /**< Signal level limiting, 0 for disable, 1 for enable, -1 if unspecified */
+    OMX_S32 nDrcEffectType;        /**< MPEG-D DRC effect type, between -1 and 6, -2 if unspecified */
+} OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE;
+
 typedef struct OMX_AUDIO_PARAM_ANDROID_PROFILETYPE {
    OMX_U32 nSize;
    OMX_VERSIONTYPE nVersion;
diff --git a/headers/media_plugin/media/openmax/OMX_IndexExt.h b/headers/media_plugin/media/openmax/OMX_IndexExt.h
index c2bf97e..716d959 100644
--- a/headers/media_plugin/media/openmax/OMX_IndexExt.h
+++ b/headers/media_plugin/media/openmax/OMX_IndexExt.h
@@ -63,6 +63,7 @@
     OMX_IndexParamAudioAndroidAacPresentation,      /**< reference: OMX_AUDIO_PARAM_ANDROID_AACPRESENTATIONTYPE */
     OMX_IndexParamAudioAndroidEac3,                 /**< reference: OMX_AUDIO_PARAM_ANDROID_EAC3TYPE */
     OMX_IndexParamAudioProfileQuerySupported,       /**< reference: OMX_AUDIO_PARAM_ANDROID_PROFILETYPE */
+    OMX_IndexParamAudioAndroidAacDrcPresentation,   /**< reference: OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE */
     OMX_IndexExtAudioEndUnused,
 
     /* Image parameters and configurations */
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 8f6b1bd..8b2f842 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -42,6 +42,7 @@
 /* list of hal interface to dump containing process during native dumps */
 static const char* hal_interfaces_to_dump[] {
         "android.hardware.audio@2.0::IDevicesFactory",
+        "android.hardware.audio@4.0::IDevicesFactory",
         "android.hardware.bluetooth@1.0::IBluetoothHci",
         "android.hardware.camera.provider@2.4::ICameraProvider",
         "android.hardware.drm@1.0::IDrmFactory",
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index 34e6d80..89bc0c4 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -92,10 +92,13 @@
     Mutex::Autolock _l(mMutex);
 
     err = addReleaseFenceLocked(item.mSlot, item.mGraphicBuffer, releaseFence);
+    if (err != OK) {
+        BI_LOGE("Failed to addReleaseFenceLocked");
+    }
 
     err = releaseBufferLocked(item.mSlot, item.mGraphicBuffer, EGL_NO_DISPLAY,
             EGL_NO_SYNC_KHR);
-    if (err != OK) {
+    if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
         BI_LOGE("Failed to release buffer: %s (%d)",
                 strerror(-err), err);
     }
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index b022a20..3615577 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -1733,13 +1733,31 @@
     ContextRef _c(dp.get(), ctx);
     egl_context_t * const c = _c.get();
 
+    // Temporary hack: eglImageCreateKHR should accept EGL_GL_COLORSPACE_LINEAR_KHR,
+    // EGL_GL_COLORSPACE_SRGB_KHR and EGL_GL_COLORSPACE_DEFAULT_EXT if
+    // EGL_EXT_image_gl_colorspace is supported, but some drivers don't like
+    // the DEFAULT value and generate an error.
+    std::vector<EGLint> strippedAttribList;
+    for (const EGLint *attr = attrib_list; attr && attr[0] != EGL_NONE; attr += 2) {
+        if (attr[0] == EGL_GL_COLORSPACE_KHR &&
+            dp->haveExtension("EGL_EXT_image_gl_colorspace")) {
+            if (attr[1] != EGL_GL_COLORSPACE_LINEAR_KHR &&
+                attr[1] != EGL_GL_COLORSPACE_SRGB_KHR) {
+                continue;
+            }
+        }
+        strippedAttribList.push_back(attr[0]);
+        strippedAttribList.push_back(attr[1]);
+    }
+    strippedAttribList.push_back(EGL_NONE);
+
     EGLImageKHR result = EGL_NO_IMAGE_KHR;
     egl_connection_t* const cnx = &gEGLImpl;
     if (cnx->dso && cnx->egl.eglCreateImageKHR) {
         result = cnx->egl.eglCreateImageKHR(
                 dp->disp.dpy,
                 c ? c->context : EGL_NO_CONTEXT,
-                target, buffer, attrib_list);
+                target, buffer, strippedAttribList.data());
     }
     return result;
 }
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index d1887ee..fb63296 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -168,6 +168,227 @@
     return needs;
 }
 
+// Generate EOTF that converts signal values to relative display light,
+// both normalized to [0, 1].
+void ProgramCache::generateEOTF(Formatter& fs, const Key& needs) {
+    switch (needs.getInputTF()) {
+        case Key::INPUT_TF_SRGB:
+            fs << R"__SHADER__(
+                float EOTF_sRGB(float srgb) {
+                    return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
+                }
+
+                vec3 EOTF_sRGB(const vec3 srgb) {
+                    return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                vec3 EOTF(const vec3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )__SHADER__";
+            break;
+        case Key::INPUT_TF_ST2084:
+            fs << R"__SHADER__(
+                vec3 EOTF(const highp vec3 color) {
+                    const highp float m1 = (2610.0 / 4096.0) / 4.0;
+                    const highp float m2 = (2523.0 / 4096.0) * 128.0;
+                    const highp float c1 = (3424.0 / 4096.0);
+                    const highp float c2 = (2413.0 / 4096.0) * 32.0;
+                    const highp float c3 = (2392.0 / 4096.0) * 32.0;
+
+                    highp vec3 tmp = pow(color, 1.0 / vec3(m2));
+                    tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+                    return pow(tmp, 1.0 / vec3(m1));
+                }
+            )__SHADER__";
+            break;
+        case Key::INPUT_TF_HLG:
+            fs << R"__SHADER__(
+                highp float EOTF_channel(const highp float channel) {
+                    const highp float a = 0.17883277;
+                    const highp float b = 0.28466892;
+                    const highp float c = 0.55991073;
+                    return channel <= 0.5 ? channel * channel / 3.0 :
+                            (exp((channel - c) / a) + b) / 12.0;
+                }
+
+                vec3 EOTF(const highp vec3 color) {
+                    return vec3(EOTF_channel(color.r), EOTF_channel(color.g),
+                            EOTF_channel(color.b));
+                }
+            )__SHADER__";
+            break;
+        default:
+            fs << R"__SHADER__(
+                vec3 EOTF(const vec3 linear) {
+                    return linear;
+                }
+            )__SHADER__";
+            break;
+    }
+}
+
+// Generate OOTF that modifies the relative scence light to relative display light.
+void ProgramCache::generateOOTF(Formatter& fs, const Key& needs) {
+    fs << R"__SHADER__(
+        highp float CalculateY(const highp vec3 color) {
+            // BT2020 standard uses the unadjusted KR = 0.2627,
+            // KB = 0.0593 luminance interpretation for RGB conversion.
+            return color.r * 0.262700 + color.g * 0.677998 + color.b * 0.059302;
+        }
+    )__SHADER__";
+
+    // Generate OOTF that modifies the relative display light.
+    switch(needs.getInputTF()) {
+        case Key::INPUT_TF_ST2084:
+            fs << R"__SHADER__(
+                highp vec3 OOTF(const highp vec3 color) {
+                    const float maxLumi = 10000.0;
+                    const float maxMasteringLumi = 1000.0;
+                    const float maxContentLumi = 1000.0;
+                    const float maxInLumi = min(maxMasteringLumi, maxContentLumi);
+                    const float maxOutLumi = 500.0;
+
+                    // Calculate Y value in XYZ color space.
+                    float colorY = CalculateY(color);
+
+                    // convert to nits first
+                    float nits = colorY * maxLumi;
+
+                    // clamp to max input luminance
+                    nits = clamp(nits, 0.0, maxInLumi);
+
+                    // scale [0.0, maxInLumi] to [0.0, maxOutLumi]
+                    if (maxInLumi <= maxOutLumi) {
+                        nits *= maxOutLumi / maxInLumi;
+                    } else {
+                        // three control points
+                        const float x0 = 10.0;
+                        const float y0 = 17.0;
+                        const float x1 = maxOutLumi * 0.75;
+                        const float y1 = x1;
+                        const float x2 = x1 + (maxInLumi - x1) / 2.0;
+                        const float y2 = y1 + (maxOutLumi - y1) * 0.75;
+
+                        // horizontal distances between the last three control points
+                        const float h12 = x2 - x1;
+                        const float h23 = maxInLumi - x2;
+                        // tangents at the last three control points
+                        const float m1 = (y2 - y1) / h12;
+                        const float m3 = (maxOutLumi - y2) / h23;
+                        const float m2 = (m1 + m3) / 2.0;
+
+                        if (nits < x0) {
+                            // scale [0.0, x0] to [0.0, y0] linearly
+                            const float slope = y0 / x0;
+                            nits *= slope;
+                        } else if (nits < x1) {
+                            // scale [x0, x1] to [y0, y1] linearly
+                            const float slope = (y1 - y0) / (x1 - x0);
+                            nits = y0 + (nits - x0) * slope;
+                        } else if (nits < x2) {
+                            // scale [x1, x2] to [y1, y2] using Hermite interp
+                            float t = (nits - x1) / h12;
+                            nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
+                                    (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
+                        } else {
+                            // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
+                            float t = (nits - x2) / h23;
+                            nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
+                                    (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
+                        }
+                    }
+
+                    // convert back to [0.0, 1.0]
+                    float targetY = nits / maxOutLumi;
+                    return color * (targetY / max(1e-6, colorY));
+                }
+            )__SHADER__";
+            break;
+        case Key::INPUT_TF_HLG:
+            fs << R"__SHADER__(
+                highp vec3 OOTF(const highp vec3 color) {
+                    const float maxOutLumi = 500.0;
+                    const float gamma = 1.2 + 0.42 * log(maxOutLumi / 1000.0) / log(10.0);
+                    // The formula is:
+                    // alpha * pow(Y, gamma - 1.0) * color + beta;
+                    // where alpha is 1.0, beta is 0.0 as recommended in
+                    // Rec. ITU-R BT.2100-1 TABLE 5.
+                    return pow(CalculateY(color), gamma - 1.0) * color;
+                }
+            )__SHADER__";
+            break;
+        default:
+            fs << R"__SHADER__(
+                highp vec3 OOTF(const highp vec3 color) {
+                    return color;
+                }
+            )__SHADER__";
+            break;
+    }
+}
+
+// Generate OETF that converts relative display light to signal values,
+// both normalized to [0, 1]
+void ProgramCache::generateOETF(Formatter& fs, const Key& needs) {
+    switch (needs.getOutputTF()) {
+        case Key::OUTPUT_TF_SRGB:
+            fs << R"__SHADER__(
+                float OETF_sRGB(const float linear) {
+                    return linear <= 0.0031308 ?
+                            linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
+                }
+
+                vec3 OETF_sRGB(const vec3 linear) {
+                    return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                vec3 OETF(const vec3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )__SHADER__";
+            break;
+        case Key::OUTPUT_TF_ST2084:
+            fs << R"__SHADER__(
+                vec3 OETF(const vec3 linear) {
+                    const float m1 = (2610.0 / 4096.0) / 4.0;
+                    const float m2 = (2523.0 / 4096.0) * 128.0;
+                    const float c1 = (3424.0 / 4096.0);
+                    const float c2 = (2413.0 / 4096.0) * 32.0;
+                    const float c3 = (2392.0 / 4096.0) * 32.0;
+
+                    vec3 tmp = pow(linear, vec3(m1));
+                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+                    return pow(tmp, vec3(m2));
+                }
+            )__SHADER__";
+            break;
+        case Key::OUTPUT_TF_HLG:
+            fs << R"__SHADER__(
+                highp float OETF_channel(const highp float channel) {
+                    const highp float a = 0.17883277;
+                    const highp float b = 0.28466892;
+                    const highp float c = 0.55991073;
+                    return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
+                            a * log(12.0 * channel - b) + c;
+                }
+
+                vec3 OETF(const highp vec3 color) {
+                    return vec3(OETF_channel(color.r), OETF_channel(color.g),
+                            OETF_channel(color.b));
+                }
+            )__SHADER__";
+            break;
+        default:
+            fs << R"__SHADER__(
+                vec3 OETF(const vec3 linear) {
+                    return linear;
+                }
+            )__SHADER__";
+            break;
+    }
+}
+
 String8 ProgramCache::generateVertexShader(const Key& needs) {
     Formatter vs;
     if (needs.isTexturing()) {
@@ -223,221 +444,9 @@
     if (needs.hasColorMatrix()) {
         fs << "uniform mat4 colorMatrix;";
 
-        // Generate EOTF that converts signal values to relative display light,
-        // both normalized to [0, 1].
-        switch (needs.getInputTF()) {
-            case Key::INPUT_TF_LINEAR:
-            default:
-                fs << R"__SHADER__(
-                    vec3 EOTF(const vec3 linear) {
-                        return linear;
-                    }
-                )__SHADER__";
-                break;
-            case Key::INPUT_TF_SRGB:
-                fs << R"__SHADER__(
-                    float EOTF_sRGB(float srgb) {
-                        return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
-                    }
-
-                    vec3 EOTF_sRGB(const vec3 srgb) {
-                        return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                    }
-
-                    vec3 EOTF(const vec3 srgb) {
-                        return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                    }
-                )__SHADER__";
-                break;
-            case Key::INPUT_TF_ST2084:
-                fs << R"__SHADER__(
-                    vec3 EOTF(const highp vec3 color) {
-                        const highp float m1 = (2610.0 / 4096.0) / 4.0;
-                        const highp float m2 = (2523.0 / 4096.0) * 128.0;
-                        const highp float c1 = (3424.0 / 4096.0);
-                        const highp float c2 = (2413.0 / 4096.0) * 32.0;
-                        const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
-                        highp vec3 tmp = pow(color, 1.0 / vec3(m2));
-                        tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
-                        return pow(tmp, 1.0 / vec3(m1));
-                    }
-                    )__SHADER__";
-                break;
-          case Key::INPUT_TF_HLG:
-              fs << R"__SHADER__(
-                  highp float EOTF_channel(const highp float channel) {
-                      const highp float a = 0.17883277;
-                      const highp float b = 0.28466892;
-                      const highp float c = 0.55991073;
-                      return channel <= 0.5 ? channel * channel / 3.0 :
-                              (exp((channel - c) / a) + b) / 12.0;
-                  }
-
-                  vec3 EOTF(const highp vec3 color) {
-                      return vec3(EOTF_channel(color.r), EOTF_channel(color.g),
-                              EOTF_channel(color.b));
-                  }
-                  )__SHADER__";
-              break;
-        }
-
-        fs << R"__SHADER__(
-            highp float CalculateY(const highp vec3 color) {
-                // BT2020 standard uses the unadjusted KR = 0.2627,
-                // KB = 0.0593 luminance interpretation for RGB conversion.
-                return color.r * 0.262700 + color.g * 0.677998 +
-                        color.b * 0.059302;
-            }
-        )__SHADER__";
-
-        // Generate OOTF that modifies the relative display light.
-        switch(needs.getInputTF()) {
-            case Key::INPUT_TF_ST2084:
-                fs << R"__SHADER__(
-                    highp vec3 OOTF(const highp vec3 color) {
-                        const float maxLumi = 10000.0;
-                        const float maxMasteringLumi = 1000.0;
-                        const float maxContentLumi = 1000.0;
-                        const float maxInLumi = min(maxMasteringLumi, maxContentLumi);
-                        const float maxOutLumi = 500.0;
-
-                        // Calculate Y value in XYZ color space.
-                        float colorY = CalculateY(color);
-
-                        // convert to nits first
-                        float nits = colorY * maxLumi;
-
-                        // clamp to max input luminance
-                        nits = clamp(nits, 0.0, maxInLumi);
-
-                        // scale [0.0, maxInLumi] to [0.0, maxOutLumi]
-                        if (maxInLumi <= maxOutLumi) {
-                            nits *= maxOutLumi / maxInLumi;
-                        } else {
-                            // three control points
-                            const float x0 = 10.0;
-                            const float y0 = 17.0;
-                            const float x1 = maxOutLumi * 0.75;
-                            const float y1 = x1;
-                            const float x2 = x1 + (maxInLumi - x1) / 2.0;
-                            const float y2 = y1 + (maxOutLumi - y1) * 0.75;
-
-                            // horizontal distances between the last three control points
-                            const float h12 = x2 - x1;
-                            const float h23 = maxInLumi - x2;
-                            // tangents at the last three control points
-                            const float m1 = (y2 - y1) / h12;
-                            const float m3 = (maxOutLumi - y2) / h23;
-                            const float m2 = (m1 + m3) / 2.0;
-
-                            if (nits < x0) {
-                                // scale [0.0, x0] to [0.0, y0] linearly
-                                const float slope = y0 / x0;
-                                nits *= slope;
-                            } else if (nits < x1) {
-                                // scale [x0, x1] to [y0, y1] linearly
-                                const float slope = (y1 - y0) / (x1 - x0);
-                                nits = y0 + (nits - x0) * slope;
-                            } else if (nits < x2) {
-                                // scale [x1, x2] to [y1, y2] using Hermite interp
-                                float t = (nits - x1) / h12;
-                                nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
-                                       (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
-                            } else {
-                                // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
-                                float t = (nits - x2) / h23;
-                                nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
-                                       (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
-                            }
-                        }
-
-                        // convert back to [0.0, 1.0]
-                        float targetY = nits / maxOutLumi;
-                        return color * (targetY / max(1e-6, colorY));
-                    }
-                )__SHADER__";
-                break;
-            case Key::INPUT_TF_HLG:
-                fs << R"__SHADER__(
-                    highp vec3 OOTF(const highp vec3 color) {
-                        const float maxOutLumi = 500.0;
-                        const float gamma = 1.2 + 0.42 * log(maxOutLumi / 1000.0) / log(10.0);
-                        // The formula is:
-                        // alpha * pow(Y, gamma - 1.0) * color + beta;
-                        // where alpha is 1.0, beta is 0.0 as recommended in
-                        // Rec. ITU-R BT.2100-1 TABLE 5.
-                        return pow(CalculateY(color), gamma - 1.0) * color;
-                    }
-                )__SHADER__";
-                break;
-            default:
-                fs << R"__SHADER__(
-                    highp vec3 OOTF(const highp vec3 color) {
-                        return color;
-                    }
-                )__SHADER__";
-        }
-
-        // Generate OETF that converts relative display light to signal values,
-        // both normalized to [0, 1]
-        switch (needs.getOutputTF()) {
-            case Key::OUTPUT_TF_LINEAR:
-            default:
-                fs << R"__SHADER__(
-                    vec3 OETF(const vec3 linear) {
-                        return linear;
-                    }
-                )__SHADER__";
-                break;
-            case Key::OUTPUT_TF_SRGB:
-                fs << R"__SHADER__(
-                    float OETF_sRGB(const float linear) {
-                        return linear <= 0.0031308 ?
-                                linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
-                    }
-
-                    vec3 OETF_sRGB(const vec3 linear) {
-                        return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                    }
-
-                    vec3 OETF(const vec3 linear) {
-                        return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                    }
-                )__SHADER__";
-                break;
-            case Key::OUTPUT_TF_ST2084:
-                fs << R"__SHADER__(
-                    vec3 OETF(const vec3 linear) {
-                        const float m1 = (2610.0 / 4096.0) / 4.0;
-                        const float m2 = (2523.0 / 4096.0) * 128.0;
-                        const float c1 = (3424.0 / 4096.0);
-                        const float c2 = (2413.0 / 4096.0) * 32.0;
-                        const float c3 = (2392.0 / 4096.0) * 32.0;
-
-                        vec3 tmp = pow(linear, vec3(m1));
-                        tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
-                        return pow(tmp, vec3(m2));
-                    }
-                )__SHADER__";
-                break;
-            case Key::OUTPUT_TF_HLG:
-                fs << R"__SHADER__(
-                    highp float OETF_channel(const highp float channel) {
-                        const highp float a = 0.17883277;
-                        const highp float b = 0.28466892;
-                        const highp float c = 0.55991073;
-                        return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
-                                a * log(12.0 * channel - b) + c;
-                    }
-
-                    vec3 OETF(const highp vec3 color) {
-                        return vec3(OETF_channel(color.r), OETF_channel(color.g),
-                                OETF_channel(color.b));
-                    }
-                )__SHADER__";
-                break;
-        }
+        generateEOTF(fs, needs);
+        generateOOTF(fs, needs);
+        generateOETF(fs, needs);
     }
 
     fs << "void main(void) {" << indent;
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.h b/services/surfaceflinger/RenderEngine/ProgramCache.h
index f67e132..d18163a 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.h
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.h
@@ -28,6 +28,7 @@
 namespace android {
 
 class Description;
+class Formatter;
 class Program;
 class String8;
 
@@ -132,6 +133,12 @@
     void primeCache();
     // compute a cache Key from a Description
     static Key computeKey(const Description& description);
+    // Generate EOTF based from Key.
+    static void generateEOTF(Formatter& fs, const Key& needs);
+    // Generate OOTF based from Key.
+    static void generateOOTF(Formatter& fs, const Key& needs);
+    // Generate OETF based from Key.
+    static void generateOETF(Formatter& fs, const Key& needs);
     // generates a program from the Key
     static Program* generateProgram(const Key& needs);
     // generates the vertex shader from the Key
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 2b8a22c..bb6ca39 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -386,6 +386,11 @@
                                  DisplayVariant<type, type, width, height, critical, Async::FALSE,
                                                 Secure::TRUE, GRALLOC_USAGE_PHYSICAL_DISPLAY>> {};
 
+// An invalid display
+using InvalidDisplayVariant =
+        DisplayVariant<DisplayDevice::DISPLAY_ID_INVALID, DisplayDevice::DISPLAY_ID_INVALID, 0, 0,
+                       Critical::FALSE, Async::FALSE, Secure::FALSE, 0>;
+
 // A primary display is a physical display that is critical
 using PrimaryDisplayVariant =
         PhysicalDisplayVariant<1001, DisplayDevice::DISPLAY_PRIMARY, 3840, 2160, Critical::TRUE>;
@@ -645,6 +650,8 @@
 using HdrDolbyVisionDisplayCase =
         Case<PrimaryDisplayVariant, WideColorNotSupportedVariant<PrimaryDisplayVariant>,
              HdrDolbyVisionSupportedVariant<PrimaryDisplayVariant>>;
+using InvalidDisplayCase = Case<InvalidDisplayVariant, WideColorSupportNotConfiguredVariant,
+                                NonHwcDisplayHdrSupportVariant>;
 
 /* ------------------------------------------------------------------------
  *
@@ -1560,7 +1567,7 @@
     // --------------------------------------------------------------------
     // Postconditions
 
-    EXPECT_EQ(newLayerStack, getDisplayDevice(display.token())->getLayerStack());
+    EXPECT_EQ(newLayerStack, display.mutableDisplayDevice()->getLayerStack());
 }
 
 TEST_F(HandleTransactionLockedTest, processesDisplayTransformChanges) {
@@ -1588,7 +1595,7 @@
     // --------------------------------------------------------------------
     // Postconditions
 
-    EXPECT_EQ(newTransform, getDisplayDevice(display.token())->getOrientation());
+    EXPECT_EQ(newTransform, display.mutableDisplayDevice()->getOrientation());
 }
 
 TEST_F(HandleTransactionLockedTest, processesDisplayViewportChanges) {
@@ -1616,7 +1623,7 @@
     // --------------------------------------------------------------------
     // Postconditions
 
-    EXPECT_EQ(newViewport, getDisplayDevice(display.token())->getViewport());
+    EXPECT_EQ(newViewport, display.mutableDisplayDevice()->getViewport());
 }
 
 TEST_F(HandleTransactionLockedTest, processesDisplayFrameChanges) {
@@ -1644,7 +1651,7 @@
     // --------------------------------------------------------------------
     // Postconditions
 
-    EXPECT_EQ(newFrame, getDisplayDevice(display.token())->getFrame());
+    EXPECT_EQ(newFrame, display.mutableDisplayDevice()->getFrame());
 }
 
 TEST_F(HandleTransactionLockedTest, processesDisplayWidthChanges) {
@@ -1729,5 +1736,579 @@
     mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
 }
 
+/* ------------------------------------------------------------------------
+ * SurfaceFlinger::setDisplayStateLocked
+ */
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingWithUnknownDisplay) {
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // We have an unknown display token not associated with a known display
+    sp<BBinder> displayToken = new BBinder();
+
+    // The requested display state references the unknown display.
+    DisplayState state;
+    state.what = DisplayState::eLayerStackChanged;
+    state.token = displayToken;
+    state.layerStack = 456;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The display token still doesn't match anything known.
+    EXPECT_FALSE(hasCurrentDisplayState(displayToken));
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingWithInvalidDisplay) {
+    using Case = InvalidDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // An invalid display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The invalid display has some state
+    display.mutableCurrentDisplayState().layerStack = 654u;
+
+    // The requested display state tries to change the display state.
+    DisplayState state;
+    state.what = DisplayState::eLayerStackChanged;
+    state.token = display.token();
+    state.layerStack = 456;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The current display layer stack value is unchanged.
+    EXPECT_EQ(654u, getCurrentDisplayState(display.token()).layerStack);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingWhenNoChanges) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is already set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // No changes are made to the display
+    DisplayState state;
+    state.what = 0;
+    state.token = display.token();
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingIfSurfaceDidNotChange) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is already set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // There is a surface that can be set.
+    sp<mock::GraphicBufferProducer> surface = new mock::GraphicBufferProducer();
+
+    // The current display state has the surface set
+    display.mutableCurrentDisplayState().surface = surface;
+
+    // The incoming request sets the same surface
+    DisplayState state;
+    state.what = DisplayState::eSurfaceChanged;
+    state.token = display.token();
+    state.surface = surface;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The current display state is unchanged.
+    EXPECT_EQ(surface.get(), display.getCurrentDisplayState().surface.get());
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfSurfaceChanged) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is already set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // There is a surface that can be set.
+    sp<mock::GraphicBufferProducer> surface = new mock::GraphicBufferProducer();
+
+    // The current display state does not have a surface
+    display.mutableCurrentDisplayState().surface = nullptr;
+
+    // The incoming request sets a surface
+    DisplayState state;
+    state.what = DisplayState::eSurfaceChanged;
+    state.token = display.token();
+    state.surface = surface;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display layer stack state is set to the new value
+    EXPECT_EQ(surface.get(), display.getCurrentDisplayState().surface.get());
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingIfLayerStackDidNotChange) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is already set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The display has a layer stack set
+    display.mutableCurrentDisplayState().layerStack = 456u;
+
+    // The incoming request sets the same layer stack
+    DisplayState state;
+    state.what = DisplayState::eLayerStackChanged;
+    state.token = display.token();
+    state.layerStack = 456u;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The current display state is unchanged
+    EXPECT_EQ(456u, display.getCurrentDisplayState().layerStack);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfLayerStackChanged) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The display has a layer stack set
+    display.mutableCurrentDisplayState().layerStack = 654u;
+
+    // The incoming request sets a different layer stack
+    DisplayState state;
+    state.what = DisplayState::eLayerStackChanged;
+    state.token = display.token();
+    state.layerStack = 456u;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The desired display state has been set to the new value.
+    EXPECT_EQ(456u, display.getCurrentDisplayState().layerStack);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingIfProjectionDidNotChange) {
+    using Case = SimplePrimaryDisplayCase;
+    constexpr int initialOrientation = 180;
+    const Rect initialFrame = {1, 2, 3, 4};
+    const Rect initialViewport = {5, 6, 7, 8};
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The current display state projection state is all set
+    display.mutableCurrentDisplayState().orientation = initialOrientation;
+    display.mutableCurrentDisplayState().frame = initialFrame;
+    display.mutableCurrentDisplayState().viewport = initialViewport;
+
+    // The incoming request sets the same projection state
+    DisplayState state;
+    state.what = DisplayState::eDisplayProjectionChanged;
+    state.token = display.token();
+    state.orientation = initialOrientation;
+    state.frame = initialFrame;
+    state.viewport = initialViewport;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The current display state is unchanged
+    EXPECT_EQ(initialOrientation, display.getCurrentDisplayState().orientation);
+
+    EXPECT_EQ(initialFrame, display.getCurrentDisplayState().frame);
+    EXPECT_EQ(initialViewport, display.getCurrentDisplayState().viewport);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfOrientationChanged) {
+    using Case = SimplePrimaryDisplayCase;
+    constexpr int initialOrientation = 90;
+    constexpr int desiredOrientation = 180;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The current display state has an orientation set
+    display.mutableCurrentDisplayState().orientation = initialOrientation;
+
+    // The incoming request sets a different orientation
+    DisplayState state;
+    state.what = DisplayState::eDisplayProjectionChanged;
+    state.token = display.token();
+    state.orientation = desiredOrientation;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display state has the new value.
+    EXPECT_EQ(desiredOrientation, display.getCurrentDisplayState().orientation);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfFrameChanged) {
+    using Case = SimplePrimaryDisplayCase;
+    const Rect initialFrame = {0, 0, 0, 0};
+    const Rect desiredFrame = {5, 6, 7, 8};
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The current display state does not have a frame
+    display.mutableCurrentDisplayState().frame = initialFrame;
+
+    // The incoming request sets a frame
+    DisplayState state;
+    state.what = DisplayState::eDisplayProjectionChanged;
+    state.token = display.token();
+    state.frame = desiredFrame;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display state has the new value.
+    EXPECT_EQ(desiredFrame, display.getCurrentDisplayState().frame);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfViewportChanged) {
+    using Case = SimplePrimaryDisplayCase;
+    const Rect initialViewport = {0, 0, 0, 0};
+    const Rect desiredViewport = {5, 6, 7, 8};
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The current display state does not have a viewport
+    display.mutableCurrentDisplayState().viewport = initialViewport;
+
+    // The incoming request sets a viewport
+    DisplayState state;
+    state.what = DisplayState::eDisplayProjectionChanged;
+    state.token = display.token();
+    state.viewport = desiredViewport;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display state has the new value.
+    EXPECT_EQ(desiredViewport, display.getCurrentDisplayState().viewport);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedDoesNothingIfSizeDidNotChange) {
+    using Case = SimplePrimaryDisplayCase;
+    constexpr uint32_t initialWidth = 1024;
+    constexpr uint32_t initialHeight = 768;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The current display state has a size set
+    display.mutableCurrentDisplayState().width = initialWidth;
+    display.mutableCurrentDisplayState().height = initialHeight;
+
+    // The incoming request sets the same display size
+    DisplayState state;
+    state.what = DisplayState::eDisplaySizeChanged;
+    state.token = display.token();
+    state.width = initialWidth;
+    state.height = initialHeight;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags are empty
+    EXPECT_EQ(0u, flags);
+
+    // The current display state is unchanged
+    EXPECT_EQ(initialWidth, display.getCurrentDisplayState().width);
+    EXPECT_EQ(initialHeight, display.getCurrentDisplayState().height);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfWidthChanged) {
+    using Case = SimplePrimaryDisplayCase;
+    constexpr uint32_t initialWidth = 0;
+    constexpr uint32_t desiredWidth = 1024;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The display does not yet have a width
+    display.mutableCurrentDisplayState().width = initialWidth;
+
+    // The incoming request sets a display width
+    DisplayState state;
+    state.what = DisplayState::eDisplaySizeChanged;
+    state.token = display.token();
+    state.width = desiredWidth;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display state has the new value.
+    EXPECT_EQ(desiredWidth, display.getCurrentDisplayState().width);
+}
+
+TEST_F(DisplayTransactionTest, setDisplayStateLockedRequestsUpdateIfHeightChanged) {
+    using Case = SimplePrimaryDisplayCase;
+    constexpr uint32_t initialHeight = 0;
+    constexpr uint32_t desiredHeight = 768;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A display is set up
+    auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+    display.inject();
+
+    // The display does not yet have a height
+    display.mutableCurrentDisplayState().height = initialHeight;
+
+    // The incoming request sets a display height
+    DisplayState state;
+    state.what = DisplayState::eDisplaySizeChanged;
+    state.token = display.token();
+    state.height = desiredHeight;
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    uint32_t flags = mFlinger.setDisplayStateLocked(state);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The returned flags indicate a transaction is needed
+    EXPECT_EQ(eDisplayTransactionNeeded, flags);
+
+    // The current display state has the new value.
+    EXPECT_EQ(desiredHeight, display.getCurrentDisplayState().height);
+}
+
+/* ------------------------------------------------------------------------
+ * SurfaceFlinger::onInitializeDisplays
+ */
+
+TEST_F(DisplayTransactionTest, onInitializeDisplaysSetsUpPrimaryDisplay) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // A primary display is set up
+    Case::Display::injectHwcDisplay(this);
+    auto primaryDisplay = Case::Display::makeFakeExistingDisplayInjector(this);
+    primaryDisplay.inject();
+
+    // --------------------------------------------------------------------
+    // Call Expectations
+
+    // We expect the surface interceptor to possibly be used, but we treat it as
+    // disabled since it is called as a side effect rather than directly by this
+    // function.
+    EXPECT_CALL(*mSurfaceInterceptor, isEnabled()).WillOnce(Return(false));
+
+    // We expect a call to get the active display config.
+    Case::Display::setupHwcGetActiveConfigCallExpectations(this);
+
+    // We expect invalidate() to be invoked once to trigger display transaction
+    // processing.
+    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    mFlinger.onInitializeDisplays();
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The primary display should have a current state
+    ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token()));
+    const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token());
+    // The layer stack state should be set to zero
+    EXPECT_EQ(0u, primaryDisplayState.layerStack);
+    // The orientation state should be set to zero
+    EXPECT_EQ(0, primaryDisplayState.orientation);
+
+    // The frame state should be set to INVALID
+    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.frame);
+
+    // The viewport state should be set to INVALID
+    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.viewport);
+
+    // The width and height should both be zero
+    EXPECT_EQ(0u, primaryDisplayState.width);
+    EXPECT_EQ(0u, primaryDisplayState.height);
+
+    // The display should be set to HWC_POWER_MODE_NORMAL
+    ASSERT_TRUE(hasDisplayDevice(primaryDisplay.token()));
+    auto displayDevice = primaryDisplay.mutableDisplayDevice();
+    EXPECT_EQ(HWC_POWER_MODE_NORMAL, displayDevice->getPowerMode());
+
+    // The display refresh period should be set in the frame tracker.
+    FrameStats stats;
+    mFlinger.getAnimFrameTracker().getStats(&stats);
+    EXPECT_EQ(DEFAULT_REFRESH_RATE, stats.refreshPeriodNano);
+
+    // The display transaction needed flag should be set.
+    EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
+
+    // The compositor timing should be set to default values
+    const auto& compositorTiming = mFlinger.getCompositorTiming();
+    EXPECT_EQ(-DEFAULT_REFRESH_RATE, compositorTiming.deadline);
+    EXPECT_EQ(DEFAULT_REFRESH_RATE, compositorTiming.interval);
+    EXPECT_EQ(DEFAULT_REFRESH_RATE, compositorTiming.presentLatency);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index b8a3baf..f689537 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -85,6 +85,17 @@
         return mFlinger->onHotplugReceived(sequenceId, display, connection);
     }
 
+    auto setDisplayStateLocked(const DisplayState& s) { return mFlinger->setDisplayStateLocked(s); }
+
+    auto onInitializeDisplays() { return mFlinger->onInitializeDisplays(); }
+
+    /* ------------------------------------------------------------------------
+     * Read-only access to private data to assert post-conditions.
+     */
+
+    const auto& getAnimFrameTracker() const { return mFlinger->mAnimFrameTracker; }
+    const auto& getCompositorTiming() const { return mFlinger->getBE().mCompositorTiming; }
+
     /* ------------------------------------------------------------------------
      * Read-write access to private data to set up preconditions and assert
      * post-conditions.
@@ -247,6 +258,16 @@
             return mFlinger.mutableCurrentState().displays.editValueFor(mDisplayToken);
         }
 
+        const auto& getDrawingDisplayState() {
+            return mFlinger.mutableDrawingState().displays.valueFor(mDisplayToken);
+        }
+
+        const auto& getCurrentDisplayState() {
+            return mFlinger.mutableCurrentState().displays.valueFor(mDisplayToken);
+        }
+
+        auto& mutableDisplayDevice() { return mFlinger.mutableDisplays().valueFor(mDisplayToken); }
+
         auto& setNativeWindow(const sp<ANativeWindow>& nativeWindow) {
             mNativeWindow = nativeWindow;
             return *this;
@@ -278,7 +299,7 @@
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
-            if (mType < DisplayDevice::DISPLAY_VIRTUAL) {
+            if (mType >= DisplayDevice::DISPLAY_PRIMARY && mType < DisplayDevice::DISPLAY_VIRTUAL) {
                 mFlinger.mutableBuiltinDisplays()[mType] = mDisplayToken;
             }