OSDN Git Service

Merge "Allow apk path to contain one subdirectory." into lmp-dev
authorCalin Juravle <calin@google.com>
Fri, 22 Aug 2014 12:58:10 +0000 (12:58 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 22 Aug 2014 12:58:11 +0000 (12:58 +0000)
12 files changed:
cmds/atrace/atrace.cpp
data/etc/android.hardware.sensor.heartrate.ecg.xml [new file with mode: 0644]
opengl/libs/GLES_trace/src/gltrace_context.cpp
opengl/libs/GLES_trace/src/gltrace_context.h
opengl/libs/GLES_trace/src/gltrace_fixup.cpp
services/sensorservice/SensorService.cpp
services/sensorservice/SensorService.h
services/surfaceflinger/DisplayDevice.cpp
services/surfaceflinger/DisplayDevice.h
services/surfaceflinger/DisplayHardware/HWComposer.cpp
services/surfaceflinger/Layer.cpp
services/surfaceflinger/SurfaceFlinger.cpp

index fb40193..9e5c910 100644 (file)
@@ -341,11 +341,56 @@ static bool setTraceBufferSizeKB(int size)
     return writeStr(k_traceBufferSizePath, str);
 }
 
+// Read the trace_clock sysfs file and return true if it matches the requested
+// value.  The trace_clock file format is:
+// local [global] counter uptime perf
+static bool isTraceClock(const char *mode)
+{
+    int fd = open(k_traceClockPath, O_RDONLY);
+    if (fd == -1) {
+        fprintf(stderr, "error opening %s: %s (%d)\n", k_traceClockPath,
+            strerror(errno), errno);
+        return false;
+    }
+
+    char buf[4097];
+    ssize_t n = read(fd, buf, 4096);
+    close(fd);
+    if (n == -1) {
+        fprintf(stderr, "error reading %s: %s (%d)\n", k_traceClockPath,
+            strerror(errno), errno);
+        return false;
+    }
+    buf[n] = '\0';
+
+    char *start = strchr(buf, '[');
+    if (start == NULL) {
+        return false;
+    }
+    start++;
+
+    char *end = strchr(start, ']');
+    if (end == NULL) {
+        return false;
+    }
+    *end = '\0';
+
+    return strcmp(mode, start) == 0;
+}
+
 // Enable or disable the kernel's use of the global clock.  Disabling the global
 // clock will result in the kernel using a per-CPU local clock.
+// Any write to the trace_clock sysfs file will reset the buffer, so only
+// update it if the requested value is not the current value.
 static bool setGlobalClockEnable(bool enable)
 {
-    return writeStr(k_traceClockPath, enable ? "global" : "local");
+    const char *clock = enable ? "global" : "local";
+
+    if (isTraceClock(clock)) {
+        return true;
+    }
+
+    return writeStr(k_traceClockPath, clock);
 }
 
 static bool setPrintTgidEnableIfPresent(bool enable)
diff --git a/data/etc/android.hardware.sensor.heartrate.ecg.xml b/data/etc/android.hardware.sensor.heartrate.ecg.xml
new file mode 100644 (file)
index 0000000..62a0de5
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<!-- Feature for devices with a hardware electrocardiography(ECG) sensor. -->
+<permissions>
+    <feature name="android.hardware.sensor.heartrate.ecg" />
+</permissions>
index 0323e8f..d1b3586 100644 (file)
@@ -133,6 +133,9 @@ GLTraceContext::GLTraceContext(int id, int version, GLTraceState *state,
         BufferedOutputStream *stream) :
     mId(id),
     mVersion(version),
+    mVersionMajor(0),
+    mVersionMinor(0),
+    mVersionParsed(false),
     mState(state),
     mBufferedOutputStream(stream),
     mElementArrayBuffers(DefaultKeyedVector<GLuint, ElementArrayBuffer*>(NULL))
@@ -149,10 +152,40 @@ int GLTraceContext::getVersion() {
     return mVersion;
 }
 
+int GLTraceContext::getVersionMajor() {
+    if (!mVersionParsed) {
+        parseGlesVersion();
+        mVersionParsed = true;
+    }
+    return mVersionMajor;
+}
+
+int GLTraceContext::getVersionMinor() {
+    if (!mVersionParsed) {
+        parseGlesVersion();
+        mVersionParsed = true;
+    }
+    return mVersionMinor;
+}
+
 GLTraceState *GLTraceContext::getGlobalTraceState() {
     return mState;
 }
 
+void GLTraceContext::parseGlesVersion() {
+    const char* str = (const char*)hooks->gl.glGetString(GL_VERSION);
+    int major, minor;
+    if (sscanf(str, "OpenGL ES-CM %d.%d", &major, &minor) != 2) {
+        if (sscanf(str, "OpenGL ES %d.%d", &major, &minor) != 2) {
+            ALOGW("Unable to parse GL_VERSION string: \"%s\"", str);
+            major = 1;
+            minor = 0;
+        }
+    }
+    mVersionMajor = major;
+    mVersionMinor = minor;
+}
+
 void GLTraceContext::resizeFBMemory(unsigned minSize) {
     if (fbcontentsSize >= minSize) {
         return;
index 4c38bba..38c7315 100644 (file)
@@ -51,6 +51,9 @@ public:
 class GLTraceContext {
     int mId;                    /* unique context id */
     int mVersion;               /* GL version, e.g: egl_connection_t::GLESv2_INDEX */
+    int mVersionMajor;          /* GL major version. Lazily parsed in getVersionX(). */
+    int mVersionMinor;          /* GL minor version. Lazily parsed in getVersionX(). */
+    bool mVersionParsed;        /* True if major and minor versions have been parsed. */
     GLTraceState *mState;       /* parent GL Trace state (for per process GL Trace State Info) */
 
     void *fbcontents;           /* memory area to read framebuffer contents */
@@ -62,6 +65,9 @@ class GLTraceContext {
     /* list of element array buffers in use. */
     DefaultKeyedVector<GLuint, ElementArrayBuffer*> mElementArrayBuffers;
 
+    /* Parses the GL version string returned from glGetString(GL_VERSION) to get find the major and
+       minor versions of the GLES API. The context must be current before calling. */
+    void parseGlesVersion();
     void resizeFBMemory(unsigned minSize);
 public:
     gl_hooks_t *hooks;
@@ -69,6 +75,8 @@ public:
     GLTraceContext(int id, int version, GLTraceState *state, BufferedOutputStream *stream);
     int getId();
     int getVersion();
+    int getVersionMajor();
+    int getVersionMinor();
     GLTraceState *getGlobalTraceState();
     void getCompressedFB(void **fb, unsigned *fbsize,
                             unsigned *fbwidth, unsigned *fbheight,
index e6d0062..be729c7 100644 (file)
 namespace android {
 namespace gltrace {
 
+GLint glGetInteger(GLTraceContext *context, GLenum param) {
+    GLint x;
+    context->hooks->gl.glGetIntegerv(param, &x);
+    return x;
+}
+
+GLint glGetVertexAttrib(GLTraceContext *context, GLuint index, GLenum pname) {
+    GLint x;
+    context->hooks->gl.glGetVertexAttribiv(index, pname, &x);
+    return x;
+}
+
+bool isUsingPixelBuffers(GLTraceContext *context) {
+    if (context->getVersionMajor() < 3) {
+        return false; // PBOs not supported prior to GLES 3.0
+    }
+    return glGetInteger(context, GL_PIXEL_UNPACK_BUFFER_BINDING) != 0;
+}
+
+bool isUsingArrayBuffers(GLTraceContext *context) {
+    return glGetInteger(context, GL_ARRAY_BUFFER_BINDING) != 0;
+}
+
+bool isUsingElementArrayBuffers(GLTraceContext *context) {
+    return glGetInteger(context, GL_ELEMENT_ARRAY_BUFFER_BINDING) != 0;
+}
+
 unsigned getBytesPerTexel(const GLenum format, const GLenum type) {
     /*
     Description from glTexImage2D spec:
@@ -156,7 +183,8 @@ void fixup_addFBContents(GLTraceContext *context, GLMessage *glmsg, FBBinding fb
 }
 
 /** Common fixup routing for glTexImage2D & glTexSubImage2D. */
-void fixup_glTexImage(int widthIndex, int heightIndex, GLMessage *glmsg, void *dataSrc) {
+void fixup_glTexImage(GLTraceContext *context, int widthIndex, int heightIndex, GLMessage *glmsg,
+                        void *dataSrc) {
     GLMessage_DataType arg_width  = glmsg->args(widthIndex);
     GLMessage_DataType arg_height = glmsg->args(heightIndex);
 
@@ -175,7 +203,7 @@ void fixup_glTexImage(int widthIndex, int heightIndex, GLMessage *glmsg, void *d
     arg_data->set_type(GLMessage::DataType::BYTE);
     arg_data->clear_rawbytes();
 
-    if (data != NULL) {
+    if (data != NULL && !isUsingPixelBuffers(context)) {
         arg_data->set_isarray(true);
         arg_data->add_rawbytes(data, bytesPerTexel * width * height);
     } else {
@@ -185,7 +213,7 @@ void fixup_glTexImage(int widthIndex, int heightIndex, GLMessage *glmsg, void *d
 }
 
 
-void fixup_glTexImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
+void fixup_glTexImage2D(GLTraceContext *context, GLMessage *glmsg, void *pointersToFixup[]) {
     /* void glTexImage2D(GLenum target,
                         GLint level,
                         GLint internalformat,
@@ -198,10 +226,10 @@ void fixup_glTexImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
     */
     int widthIndex = 3;
     int heightIndex = 4;
-    fixup_glTexImage(widthIndex, heightIndex, glmsg, pointersToFixup[0]);
+    fixup_glTexImage(context, widthIndex, heightIndex, glmsg, pointersToFixup[0]);
 }
 
-void fixup_glTexSubImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
+void fixup_glTexSubImage2D(GLTraceContext *context, GLMessage *glmsg, void *pointersToFixup[]) {
     /*
     void glTexSubImage2D(GLenum target,
                         GLint level,
@@ -215,10 +243,11 @@ void fixup_glTexSubImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
     */
     int widthIndex = 4;
     int heightIndex = 5;
-    fixup_glTexImage(widthIndex, heightIndex, glmsg, pointersToFixup[0]);
+    fixup_glTexImage(context, widthIndex, heightIndex, glmsg, pointersToFixup[0]);
 }
 
-void fixup_glCompressedTexImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
+void fixup_glCompressedTexImage2D(GLTraceContext *context, GLMessage *glmsg,
+                                    void *pointersToFixup[]) {
     /* void glCompressedTexImage2D(GLenum target,
                                    GLint level,
                                    GLenum internalformat,
@@ -235,7 +264,7 @@ void fixup_glCompressedTexImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
     arg_data->set_type(GLMessage::DataType::BYTE);
     arg_data->clear_rawbytes();
 
-    if (data != NULL) {
+    if (data != NULL && !isUsingPixelBuffers(context)) {
         arg_data->set_isarray(true);
         arg_data->add_rawbytes(data, size);
     } else {
@@ -244,7 +273,8 @@ void fixup_glCompressedTexImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
     }
 }
 
-void fixup_glCompressedTexSubImage2D(GLMessage *glmsg, void *pointersToFixup[]) {
+void fixup_glCompressedTexSubImage2D(GLTraceContext *context, GLMessage *glmsg,
+                                        void *pointersToFixup[]) {
     /* void glCompressedTexSubImage2D(GLenum target,
                                       GLint level,
                                       GLint xoffset,
@@ -262,7 +292,7 @@ void fixup_glCompressedTexSubImage2D(GLMessage *glmsg, void *pointersToFixup[])
     arg_data->set_type(GLMessage::DataType::BYTE);
     arg_data->clear_rawbytes();
 
-    if (data != NULL) {
+    if (data != NULL && !isUsingPixelBuffers(context)) {
         arg_data->set_isarray(true);
         arg_data->add_rawbytes(data, size);
     } else {
@@ -436,26 +466,6 @@ void fixup_glGetActiveAttribOrUniform(GLTraceContext *context, GLMessage *glmsg,
     arg_location->add_intvalue(location);
 }
 
-GLint glGetInteger(GLTraceContext *context, GLenum param) {
-    GLint x;
-    context->hooks->gl.glGetIntegerv(param, &x);
-    return x;
-}
-
-GLint glGetVertexAttrib(GLTraceContext *context, GLuint index, GLenum pname) {
-    GLint x;
-    context->hooks->gl.glGetVertexAttribiv(index, pname, &x);
-    return x;
-}
-
-bool isUsingArrayBuffers(GLTraceContext *context) {
-    return glGetInteger(context, GL_ARRAY_BUFFER_BINDING) != 0;
-}
-
-bool isUsingElementArrayBuffers(GLTraceContext *context) {
-    return glGetInteger(context, GL_ELEMENT_ARRAY_BUFFER_BINDING) != 0;
-}
-
 /** Copy @len bytes of data from @src into the @dataIndex'th argument of the message. */
 void addGlBufferData(GLMessage *glmsg, int dataIndex, GLvoid *src, GLsizeiptr len) {
     GLMessage_DataType *arg_datap = glmsg->mutable_args(dataIndex);
@@ -809,22 +819,22 @@ void fixupGLMessage(GLTraceContext *context, nsecs_t wallStart, nsecs_t wallEnd,
         break;
     case GLMessage::glTexImage2D:
         if (context->getGlobalTraceState()->shouldCollectTextureDataOnGlTexImage()) {
-            fixup_glTexImage2D(glmsg, pointersToFixup);
+            fixup_glTexImage2D(context, glmsg, pointersToFixup);
         }
         break;
     case GLMessage::glTexSubImage2D:
         if (context->getGlobalTraceState()->shouldCollectTextureDataOnGlTexImage()) {
-            fixup_glTexSubImage2D(glmsg, pointersToFixup);
+            fixup_glTexSubImage2D(context, glmsg, pointersToFixup);
         }
         break;
     case GLMessage::glCompressedTexImage2D:
         if (context->getGlobalTraceState()->shouldCollectTextureDataOnGlTexImage()) {
-            fixup_glCompressedTexImage2D(glmsg, pointersToFixup);
+            fixup_glCompressedTexImage2D(context, glmsg, pointersToFixup);
         }
         break;
     case GLMessage::glCompressedTexSubImage2D:
         if (context->getGlobalTraceState()->shouldCollectTextureDataOnGlTexImage()) {
-            fixup_glCompressedTexSubImage2D(glmsg, pointersToFixup);
+            fixup_glCompressedTexSubImage2D(context, glmsg, pointersToFixup);
         }
         break;
     case GLMessage::glShaderSource:
index bee5062..84d3b0f 100644 (file)
@@ -924,6 +924,7 @@ SensorService::SensorEventConnection::SensorEventConnection(
     }
 #if DEBUG_CONNECTIONS
     mEventsReceived = mEventsSentFromCache = mEventsSent = 0;
+    mTotalAcksNeeded = mTotalAcksReceived = 0;
 #endif
 }
 
@@ -960,12 +961,15 @@ void SensorService::SensorEventConnection::dump(String8& result) {
                             mCacheSize,
                             mMaxCacheSize);
 #if DEBUG_CONNECTIONS
-        result.appendFormat("\t events recvd: %d | sent %d | cache %d | dropped %d\n",
+        result.appendFormat("\t events recvd: %d | sent %d | cache %d | dropped %d |"
+                                  " total_acks_needed %d | total_acks_recvd %d\n",
                                         mEventsReceived,
                                         mEventsSent,
                                         mEventsSentFromCache,
-                                        mEventsReceived - (mEventsSentFromCache
-                                                           mEventsSent + mCacheSize));
+                                        mEventsReceived - (mEventsSentFromCache +
+                                                           mEventsSent + mCacheSize),
+                                        mTotalAcksNeeded,
+                                        mTotalAcksReceived);
 #endif
 
     }
@@ -1125,6 +1129,9 @@ status_t SensorService::SensorEventConnection::sendEvents(
     if (index_wake_up_event >= 0) {
         scratch[index_wake_up_event].flags |= WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
         ++mWakeLockRefCount;
+#if DEBUG_CONNECTIONS
+        ++mTotalAcksNeeded;
+#endif
     }
 
     // NOTE: ASensorEvent and sensors_event_t are the same type.
@@ -1136,6 +1143,9 @@ status_t SensorService::SensorEventConnection::sendEvents(
             // If there was a wake_up sensor_event, reset the flag.
             scratch[index_wake_up_event].flags &= ~WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
             --mWakeLockRefCount;
+#if DEBUG_CONNECTIONS
+            --mTotalAcksNeeded;
+#endif
         }
         if (mEventCache == NULL) {
             mMaxCacheSize = computeMaxCacheSizeLocked();
@@ -1214,6 +1224,9 @@ void SensorService::SensorEventConnection::writeToSocketFromCacheLocked() {
             mEventCache[index_wake_up_event + numEventsSent].flags |=
                     WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
             ++mWakeLockRefCount;
+#if DEBUG_CONNECTIONS
+            ++mTotalAcksNeeded;
+#endif
         }
 
         ssize_t size = SensorEventQueue::write(mChannel,
@@ -1225,6 +1238,9 @@ void SensorService::SensorEventConnection::writeToSocketFromCacheLocked() {
                 mEventCache[index_wake_up_event + numEventsSent].flags  &=
                         ~WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
                 --mWakeLockRefCount;
+#if DEBUG_CONNECTIONS
+                --mTotalAcksNeeded;
+#endif
             }
             memmove(mEventCache, &mEventCache[numEventsSent],
                                  (mCacheSize - numEventsSent) * sizeof(sensors_event_t));
@@ -1333,6 +1349,9 @@ int SensorService::SensorEventConnection::handleEvent(int fd, int events, void*
         {
            Mutex::Autolock _l(mConnectionLock);
            --mWakeLockRefCount;
+#if DEBUG_CONNECTIONS
+           ++mTotalAcksReceived;
+#endif
         }
         // Check if wakelock can be released by sensorservice. mConnectionLock needs to be released
         // here as checkWakeLockState() will need it.
@@ -1380,7 +1399,7 @@ int SensorService::SensorEventConnection::computeMaxCacheSizeLocked() const {
    if (fifoWakeUpSensors + fifoNonWakeUpSensors == 0) {
        // It is extremely unlikely that there is a write failure in non batch mode. Return a cache
        // size that is equal to that of the batch mode.
-       ALOGI("Write failure in non-batch mode");
+       // ALOGW("Write failure in non-batch mode");
        return MAX_SOCKET_BUFFER_SIZE_BATCHED/sizeof(sensors_event_t);
    }
    return fifoWakeUpSensors + fifoNonWakeUpSensors;
index 15759ae..77f6f6a 100644 (file)
@@ -142,6 +142,7 @@ class SensorService :
 
 #if DEBUG_CONNECTIONS
         int mEventsReceived, mEventsSent, mEventsSentFromCache;
+        int mTotalAcksNeeded, mTotalAcksReceived;
 #endif
 
     public:
index 2213259..bf42b77 100644 (file)
@@ -59,7 +59,8 @@ DisplayDevice::DisplayDevice(
         const sp<DisplaySurface>& displaySurface,
         const sp<IGraphicBufferProducer>& producer,
         EGLConfig config)
-    : mFlinger(flinger),
+    : lastCompositionHadVisibleLayers(false),
+      mFlinger(flinger),
       mType(type), mHwcDisplayId(hwcId),
       mDisplayToken(displayToken),
       mDisplaySurface(displaySurface),
@@ -401,6 +402,11 @@ status_t DisplayDevice::orientationToTransfrom(
 void DisplayDevice::setDisplaySize(const int newWidth, const int newHeight) {
     dirtyRegion.set(getBounds());
 
+    if (mSurface != EGL_NO_SURFACE) {
+        eglDestroySurface(mDisplay, mSurface);
+        mSurface = EGL_NO_SURFACE;
+    }
+
     mDisplaySurface->resizeBuffers(newWidth, newHeight);
 
     ANativeWindow* const window = mNativeWindow.get();
index 00e0918..d3f784a 100644 (file)
@@ -53,6 +53,7 @@ public:
     mutable Region swapRegion;
     // region in screen space
     Region undefinedRegion;
+    bool lastCompositionHadVisibleLayers;
 
     enum DisplayType {
         DISPLAY_ID_INVALID = -1,
index 53409d1..bd07d24 100644 (file)
@@ -799,6 +799,9 @@ void HWComposer::disconnectDisplay(int disp) {
     dd.lastRetireFence = Fence::NO_FENCE;
     dd.lastDisplayFence = Fence::NO_FENCE;
     dd.outbufAcquireFence = Fence::NO_FENCE;
+    // clear all the previous configs and repopulate when a new
+    // device is added
+    dd.configs.clear();
 }
 
 int HWComposer::getVisualID() const {
index 2bbb223..a36ddd9 100644 (file)
@@ -348,8 +348,17 @@ FloatRect Layer::computeCrop(const sp<const DisplayDevice>& hw) const {
         int winWidth = s.active.w;
         int winHeight = s.active.h;
         if (invTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-            invTransform ^= NATIVE_WINDOW_TRANSFORM_FLIP_V |
-                    NATIVE_WINDOW_TRANSFORM_FLIP_H;
+            // If the activeCrop has been rotate the ends are rotated but not
+            // the space itself so when transforming ends back we can't rely on
+            // a modification of the axes of rotation. To account for this we
+            // need to reorient the inverse rotation in terms of the current
+            // axes of rotation.
+            bool is_h_flipped = (invTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) != 0;
+            bool is_v_flipped = (invTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) != 0;
+            if (is_h_flipped == is_v_flipped) {
+                invTransform ^= NATIVE_WINDOW_TRANSFORM_FLIP_V |
+                        NATIVE_WINDOW_TRANSFORM_FLIP_H;
+            }
             winWidth = s.active.h;
             winHeight = s.active.w;
         }
index b9c70e0..4070f03 100644 (file)
@@ -987,9 +987,32 @@ void SurfaceFlinger::rebuildLayerStacks() {
 
 void SurfaceFlinger::setUpHWComposer() {
     for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
-        bool mustRecompose =
-                !(mDisplays[dpy]->getDirtyRegion(false).isEmpty());
+        bool dirty = !mDisplays[dpy]->getDirtyRegion(false).isEmpty();
+        bool empty = mDisplays[dpy]->getVisibleLayersSortedByZ().size() == 0;
+        bool wasEmpty = !mDisplays[dpy]->lastCompositionHadVisibleLayers;
+
+        // If nothing has changed (!dirty), don't recompose.
+        // If something changed, but we don't currently have any visible layers,
+        //   and didn't when we last did a composition, then skip it this time.
+        // The second rule does two things:
+        // - When all layers are removed from a display, we'll emit one black
+        //   frame, then nothing more until we get new layers.
+        // - When a display is created with a private layer stack, we won't
+        //   emit any black frames until a layer is added to the layer stack.
+        bool mustRecompose = dirty && !(empty && wasEmpty);
+
+        ALOGV_IF(mDisplays[dpy]->getDisplayType() == DisplayDevice::DISPLAY_VIRTUAL,
+                "dpy[%zu]: %s composition (%sdirty %sempty %swasEmpty)", dpy,
+                mustRecompose ? "doing" : "skipping",
+                dirty ? "+" : "-",
+                empty ? "+" : "-",
+                wasEmpty ? "+" : "-");
+
         mDisplays[dpy]->beginFrame(mustRecompose);
+
+        if (mustRecompose) {
+            mDisplays[dpy]->lastCompositionHadVisibleLayers = !empty;
+        }
     }
 
     HWComposer& hwc(getHwComposer());