libvulkan: reduce log level for swapchain usage errors
am: 42a9eecb23

* commit '42a9eecb23c0b72bbee1eb2ef6b0d6586159d1c3':
  libvulkan: reduce log level for swapchain usage errors

Change-Id: I8b053f14678f9e03497ac2b621e117df581d9779
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 1add346..e293164 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -54,7 +54,7 @@
 static char cmdline_buf[16384] = "(unknown)";
 static const char *dump_traces_path = NULL;
 
-// TODO: should be part of dumpstate object
+// TODO: variables below should be part of dumpstate object
 static unsigned long id;
 static char build_type[PROPERTY_VALUE_MAX];
 static time_t now;
@@ -68,7 +68,7 @@
 
 #define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
 
-#define RAFT_DIR "/data/misc/raft/"
+#define RAFT_DIR "/data/misc/raft"
 #define RECOVERY_DIR "/cache/recovery"
 #define RECOVERY_DATA_DIR "/data/misc/recovery"
 #define LOGPERSIST_DATA_DIR "/data/misc/logd"
@@ -218,6 +218,40 @@
     }
 }
 
+static void dump_raft() {
+    if (is_user_build()) {
+        return;
+    }
+
+    std::string raft_log_path = bugreport_dir + "/raft_log.txt";
+    if (raft_log_path.empty()) {
+        MYLOGD("raft_log_path is empty\n");
+        return;
+    }
+
+    struct stat s;
+    if (stat(RAFT_DIR, &s) != 0 || !S_ISDIR(s.st_mode)) {
+        MYLOGD("%s does not exist or is not a directory\n", RAFT_DIR);
+        return;
+    }
+
+    if (!zip_writer) {
+        // Write compressed and encoded raft logs to stdout if not zip_writer.
+        run_command("RAFT LOGS", 600, "logcompressor", "-r", RAFT_DIR, NULL);
+        return;
+    }
+
+    run_command("RAFT LOGS", 600, "logcompressor", "-n", "-r", RAFT_DIR,
+            "-o", raft_log_path.c_str(), NULL);
+    if (!add_zip_entry("raft_log.txt", raft_log_path)) {
+        MYLOGE("Unable to add raft log %s to zip file\n", raft_log_path.c_str());
+    } else {
+        if (remove(raft_log_path.c_str())) {
+            MYLOGE("Error removing raft file %s: %s\n", raft_log_path.c_str(), strerror(errno));
+        }
+    }
+}
+
 static bool skip_not_stat(const char *path) {
     static const char stat[] = "/stat";
     size_t len = strlen(path);
@@ -701,8 +735,6 @@
 
     run_command("LOG STATISTICS", 10, "logcat", "-b", "all", "-S", NULL);
 
-    run_command("RAFT LOGS", 600, SU_PATH, "root", "logcompressor", "-r", RAFT_DIR, NULL);
-
     /* show the traces we collected in main(), if that was done */
     if (dump_traces_path != NULL) {
         dump_file("VM TRACES JUST NOW", dump_traces_path);
@@ -1275,6 +1307,9 @@
     // Dumps systrace right away, otherwise it will be filled with unnecessary events.
     dump_systrace();
 
+    // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
+    dump_raft();
+
     // Invoking the following dumpsys calls before dump_traces() to try and
     // keep the system stats as close to its initial state as possible.
     run_command_as_shell("DUMPSYS MEMINFO", 30, "dumpsys", "-t", "30", "meminfo", "-a", NULL);
diff --git a/cmds/installd/commands.cpp b/cmds/installd/commands.cpp
index c0c91da..8360a87 100644
--- a/cmds/installd/commands.cpp
+++ b/cmds/installd/commands.cpp
@@ -1385,6 +1385,28 @@
     return analyse_profiles(uid, pkgname);
 }
 
+static const char* parse_null(const char* arg) {
+    if (strcmp(arg, "!") == 0) {
+        return nullptr;
+    } else {
+        return arg;
+    }
+}
+
+int dexopt(const char* params[DEXOPT_PARAM_COUNT]) {
+    return dexopt(params[0],                    // apk_path
+                  atoi(params[1]),              // uid
+                  params[2],                    // pkgname
+                  params[3],                    // instruction_set
+                  atoi(params[4]),              // dexopt_needed
+                  params[5],                    // oat_dir
+                  atoi(params[6]),              // dexopt_flags
+                  params[7],                    // compiler_filter
+                  parse_null(params[8]),        // volume_uuid
+                  parse_null(params[9]));       // shared_libraries
+    static_assert(DEXOPT_PARAM_COUNT == 10U, "Unexpected dexopt param count");
+}
+
 int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* instruction_set,
            int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
            const char* volume_uuid ATTRIBUTE_UNUSED, const char* shared_libraries)
diff --git a/cmds/installd/commands.h b/cmds/installd/commands.h
index 7a42c5c..c0c39c5 100644
--- a/cmds/installd/commands.h
+++ b/cmds/installd/commands.h
@@ -56,9 +56,21 @@
 
 bool dump_profile(uid_t uid, const char *pkgname, const char *dex_files);
 
-int dexopt(const char *apk_path, uid_t uid, const char *pkgName, const char *instruction_set,
-           int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
-           const char* volume_uuid, const char* shared_libraries);
+int dexopt(const char *apk_path,
+           uid_t uid,
+           const char *pkgName,
+           const char *instruction_set,
+           int dexopt_needed,
+           const char* oat_dir,
+           int dexopt_flags,
+           const char* compiler_filter,
+           const char* volume_uuid,
+           const char* shared_libraries);
+static_assert(DEXOPT_PARAM_COUNT == 10U, "Unexpected dexopt param size");
+
+// Helper for the above, converting arguments.
+int dexopt(const char* params[DEXOPT_PARAM_COUNT]);
+
 int mark_boot_complete(const char *instruction_set);
 int linklib(const char* uuid, const char* pkgname, const char* asecLibDir, int userId);
 int idmap(const char *target_path, const char *overlay_path, uid_t uid);
diff --git a/cmds/installd/installd.cpp b/cmds/installd/installd.cpp
index 061359e..9d2f71b 100644
--- a/cmds/installd/installd.cpp
+++ b/cmds/installd/installd.cpp
@@ -219,7 +219,8 @@
 // We use otapreopt_chroot to get into the chroot.
 static constexpr const char* kOtaPreopt = "/system/bin/otapreopt_chroot";
 
-static int do_ota_dexopt(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
+static int do_ota_dexopt(const char* args[DEXOPT_PARAM_COUNT],
+                         char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
     // Time to fork and run otapreopt.
 
     // Check that the tool exists.
@@ -231,12 +232,14 @@
 
     pid_t pid = fork();
     if (pid == 0) {
-        const char* argv[1 + 9 + 1];
+        const char* argv[1 + DEXOPT_PARAM_COUNT + 1];
         argv[0] = kOtaPreopt;
-        for (size_t i = 1; i <= 9; ++i) {
-            argv[i] = arg[i - 1];
+
+        for (size_t i = 0; i < DEXOPT_PARAM_COUNT; ++i) {
+            argv[i + 1] = args[i];
         }
-        argv[10] = nullptr;
+
+        argv[DEXOPT_PARAM_COUNT + 1] = nullptr;
 
         execv(argv[0], (char * const *)argv);
         PLOG(ERROR) << "execv(OTAPREOPT_CHROOT) failed";
@@ -252,22 +255,30 @@
     }
 }
 
+static int do_regular_dexopt(const char* args[DEXOPT_PARAM_COUNT],
+                             char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
+    return dexopt(args);
+}
+
+using DexoptFn = int (*)(const char* args[DEXOPT_PARAM_COUNT],
+                         char reply[REPLY_MAX]);
+
 static int do_dexopt(char **arg, char reply[REPLY_MAX])
 {
-    int dexopt_flags = atoi(arg[6]);
-    if ((dexopt_flags & DEXOPT_OTA) != 0) {
-      return do_ota_dexopt(arg, reply);
+    const char* args[DEXOPT_PARAM_COUNT];
+    for (size_t i = 0; i < DEXOPT_PARAM_COUNT; ++i) {
+        CHECK(arg[i] != nullptr);
+        args[i] = arg[i];
     }
-    return dexopt(arg[0],                      // apk_path
-                  atoi(arg[1]),                // uid
-                  arg[2],                      // pkgname
-                  arg[3],                      // instruction_set
-                  atoi(arg[4]),                // dexopt_needed
-                  arg[5],                      // oat_dir
-                  dexopt_flags,
-                  arg[7],                      // compiler_filter
-                  parse_null(arg[8]),          // volume_uuid
-                  parse_null(arg[9]));         // shared_libraries
+
+    int dexopt_flags = atoi(arg[6]);
+    DexoptFn dexopt_fn;
+    if ((dexopt_flags & DEXOPT_OTA) != 0) {
+        dexopt_fn = do_ota_dexopt;
+    } else {
+        dexopt_fn = do_regular_dexopt;
+    }
+    return dexopt_fn(args, reply);
 }
 
 static int do_merge_profiles(char **arg, char reply[REPLY_MAX])
diff --git a/cmds/installd/installd_constants.h b/cmds/installd/installd_constants.h
index 8513695..823b8ee 100644
--- a/cmds/installd/installd_constants.h
+++ b/cmds/installd/installd_constants.h
@@ -21,6 +21,8 @@
 namespace android {
 namespace installd {
 
+constexpr size_t DEXOPT_PARAM_COUNT = 10U;
+
 /* elements combined with a valid package name to form paths */
 
 constexpr const char* PRIMARY_USER_PREFIX = "data/";
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index ac511ec..3047198 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -188,12 +188,14 @@
 
     bool ReadPackage(int argc ATTRIBUTE_UNUSED, char** argv) {
         size_t index = 0;
-        while (index < ARRAY_SIZE(package_parameters_) &&
+        static_assert(DEXOPT_PARAM_COUNT == ARRAY_SIZE(package_parameters_),
+                      "Unexpected dexopt param count");
+        while (index < DEXOPT_PARAM_COUNT &&
                 argv[index + 1] != nullptr) {
             package_parameters_[index] = argv[index + 1];
             index++;
         }
-        if (index != ARRAY_SIZE(package_parameters_)) {
+        if (index != ARRAY_SIZE(package_parameters_) || argv[index + 1] != nullptr) {
             LOG(ERROR) << "Wrong number of parameters";
             return false;
         }
@@ -357,17 +359,7 @@
     }
 
     int RunPreopt() {
-        int ret = dexopt(package_parameters_[0],          // apk_path
-                atoi(package_parameters_[1]),             // uid
-                package_parameters_[2],                   // pkgname
-                package_parameters_[3],                   // instruction_set
-                atoi(package_parameters_[4]),             // dexopt_needed
-                package_parameters_[5],                   // oat_dir
-                atoi(package_parameters_[6]),             // dexopt_flags
-                package_parameters_[7],                   // compiler_filter
-                ParseNull(package_parameters_[8]),        // volume_uuid
-                ParseNull(package_parameters_[9]));       // shared_libraries
-        return ret;
+        return dexopt(package_parameters_);
     }
 
     ////////////////////////////////////
@@ -484,7 +476,7 @@
     // to compile, instead of the A properties we could get from init/get_property.
     SystemProperties system_properties_;
 
-    const char* package_parameters_[10];
+    const char* package_parameters_[DEXOPT_PARAM_COUNT];
 
     // Store environment values we need to set.
     std::vector<std::string> environ_;
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index f7f69a9..be0ff2e 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -22,6 +22,8 @@
 #include <android-base/macros.h>
 #include <android-base/stringprintf.h>
 
+#include <installd_constants.h>
+
 #ifndef LOG_TAG
 #define LOG_TAG "otapreopt"
 #endif
@@ -78,13 +80,13 @@
 
     // Now go on and run otapreopt.
 
-    const char* argv[1 + 9 + 1];
-    CHECK_EQ(argc, 10);
+    const char* argv[1 + DEXOPT_PARAM_COUNT + 1];
+    CHECK_EQ(static_cast<size_t>(argc), DEXOPT_PARAM_COUNT + 1);
     argv[0] = "/system/bin/otapreopt";
-    for (size_t i = 1; i <= 9; ++i) {
+    for (size_t i = 1; i <= DEXOPT_PARAM_COUNT; ++i) {
         argv[i] = arg[i];
     }
-    argv[10] = nullptr;
+    argv[DEXOPT_PARAM_COUNT + 1] = nullptr;
 
     execv(argv[0], (char * const *)argv);
     PLOG(ERROR) << "execv(OTAPREOPT) failed.";
diff --git a/data/etc/wearable_core_hardware.xml b/data/etc/wearable_core_hardware.xml
index 4b7a706..4ff00b5 100644
--- a/data/etc/wearable_core_hardware.xml
+++ b/data/etc/wearable_core_hardware.xml
@@ -34,7 +34,6 @@
 
     <!-- device administration -->
     <feature name="android.software.device_admin" />
-    <feature name="android.software.managed_users" />
 
     <!-- devices with GPS must include device/google/clockwork/gps.xml -->
     <!-- devices with an autofocus camera and/or flash must include either
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index 67e28da..a17c57a 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -757,7 +757,15 @@
     /** Copy key. */
     AKEYCODE_COPY = 278,
     /** Paste key. */
-    AKEYCODE_PASTE = 279
+    AKEYCODE_PASTE = 279,
+    /** fingerprint navigation key, up. */
+    AKEYCODE_FP_NAV_UP = 280,
+    /** fingerprint navigation key, down. */
+    AKEYCODE_FP_NAV_DOWN = 281,
+    /** fingerprint navigation key, left. */
+    AKEYCODE_FP_NAV_LEFT = 282,
+    /** fingerprint navigation key, right. */
+    AKEYCODE_FP_NAV_RIGHT = 283
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/include/gui/BufferQueueConsumer.h b/include/gui/BufferQueueConsumer.h
index b2daae4..a9fce1a 100644
--- a/include/gui/BufferQueueConsumer.h
+++ b/include/gui/BufferQueueConsumer.h
@@ -136,6 +136,10 @@
     // Retrieve the sideband buffer stream, if any.
     virtual sp<NativeHandle> getSidebandStream() const;
 
+    // See IGraphicBufferConsumer::getOccupancyHistory
+    virtual status_t getOccupancyHistory(bool forceFlush,
+            std::vector<OccupancyTracker::Segment>* outHistory) override;
+
     // dump our state in a String
     virtual void dump(String8& result, const char* prefix) const;
 
diff --git a/include/gui/BufferQueueCore.h b/include/gui/BufferQueueCore.h
index 4337da9..6c69d69 100644
--- a/include/gui/BufferQueueCore.h
+++ b/include/gui/BufferQueueCore.h
@@ -20,6 +20,7 @@
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/BufferSlot.h>
+#include <gui/OccupancyTracker.h>
 
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
@@ -322,6 +323,8 @@
     // The slot of the last queued buffer
     int mLastQueuedSlot;
 
+    OccupancyTracker mOccupancyTracker;
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/include/gui/ConsumerBase.h b/include/gui/ConsumerBase.h
index 9307a26..d1f4cdd 100644
--- a/include/gui/ConsumerBase.h
+++ b/include/gui/ConsumerBase.h
@@ -85,6 +85,10 @@
     // See IGraphicBufferConsumer::setDefaultBufferDataSpace
     status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace);
 
+    // See IGraphicBufferConsumer::getOccupancyHistory
+    status_t getOccupancyHistory(bool forceFlush,
+            std::vector<OccupancyTracker::Segment>* outHistory);
+
 private:
     ConsumerBase(const ConsumerBase&);
     void operator=(const ConsumerBase&);
diff --git a/include/gui/IGraphicBufferConsumer.h b/include/gui/IGraphicBufferConsumer.h
index e983c16..4915478 100644
--- a/include/gui/IGraphicBufferConsumer.h
+++ b/include/gui/IGraphicBufferConsumer.h
@@ -27,6 +27,7 @@
 #include <binder/IInterface.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
+#include <gui/OccupancyTracker.h>
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
@@ -265,6 +266,12 @@
     // Retrieve the sideband buffer stream, if any.
     virtual sp<NativeHandle> getSidebandStream() const = 0;
 
+    // Retrieves any stored segments of the occupancy history of this
+    // BufferQueue and clears them. Optionally closes out the pending segment if
+    // forceFlush is true.
+    virtual status_t getOccupancyHistory(bool forceFlush,
+            std::vector<OccupancyTracker::Segment>* outHistory) = 0;
+
     // dump state into a string
     virtual void dump(String8& result, const char* prefix) const = 0;
 
diff --git a/include/gui/OccupancyTracker.h b/include/gui/OccupancyTracker.h
new file mode 100644
index 0000000..1d15e7f
--- /dev/null
+++ b/include/gui/OccupancyTracker.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_GUI_OCCUPANCYTRACKER_H
+#define ANDROID_GUI_OCCUPANCYTRACKER_H
+
+#include <binder/Parcelable.h>
+
+#include <utils/Timers.h>
+
+#include <deque>
+#include <unordered_map>
+
+namespace android {
+
+class String8;
+
+class OccupancyTracker
+{
+public:
+    OccupancyTracker()
+      : mPendingSegment(),
+        mSegmentHistory(),
+        mLastOccupancy(0),
+        mLastOccupancyChangeTime(0) {}
+
+    struct Segment : public Parcelable {
+        Segment()
+          : totalTime(0),
+            numFrames(0),
+            occupancyAverage(0.0f),
+            usedThirdBuffer(false) {}
+
+        Segment(nsecs_t totalTime, size_t numFrames, float occupancyAverage,
+                bool usedThirdBuffer)
+          : totalTime(totalTime),
+            numFrames(numFrames),
+            occupancyAverage(occupancyAverage),
+            usedThirdBuffer(usedThirdBuffer) {}
+
+        // Parcelable interface
+        virtual status_t writeToParcel(Parcel* parcel) const override;
+        virtual status_t readFromParcel(const Parcel* parcel) override;
+
+        nsecs_t totalTime;
+        size_t numFrames;
+
+        // Average occupancy of the queue over this segment. (0.0, 1.0) implies
+        // double-buffered, (1.0, 2.0) implies triple-buffered.
+        float occupancyAverage;
+
+        // Whether a third buffer was used at all during this segment (since a
+        // segment could read as double-buffered on average, but still require a
+        // third buffer to avoid jank for some smaller portion)
+        bool usedThirdBuffer;
+    };
+
+    void registerOccupancyChange(size_t occupancy);
+    std::vector<Segment> getSegmentHistory(bool forceFlush);
+
+private:
+    static constexpr size_t MAX_HISTORY_SIZE = 10;
+    static constexpr nsecs_t NEW_SEGMENT_DELAY = ms2ns(100);
+    static constexpr size_t LONG_SEGMENT_THRESHOLD = 3;
+
+    struct PendingSegment {
+        void clear() {
+            totalTime = 0;
+            numFrames = 0;
+            mOccupancyTimes.clear();
+        }
+
+        nsecs_t totalTime;
+        size_t numFrames;
+        std::unordered_map<size_t, nsecs_t> mOccupancyTimes;
+    };
+
+    void recordPendingSegment();
+
+    PendingSegment mPendingSegment;
+    std::deque<Segment> mSegmentHistory;
+
+    size_t mLastOccupancy;
+    nsecs_t mLastOccupancyChangeTime;
+
+}; // class OccupancyTracker
+
+} // namespace android
+
+#endif
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index b7012eb..542f647 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -319,6 +319,10 @@
     DEFINE_KEYCODE(CUT),
     DEFINE_KEYCODE(COPY),
     DEFINE_KEYCODE(PASTE),
+    DEFINE_KEYCODE(FP_NAV_UP),
+    DEFINE_KEYCODE(FP_NAV_DOWN),
+    DEFINE_KEYCODE(FP_NAV_LEFT),
+    DEFINE_KEYCODE(FP_NAV_RIGHT),
 
     { NULL, 0 }
 };
diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk
index 6e92a47..3e30bb2 100644
--- a/libs/gui/Android.mk
+++ b/libs/gui/Android.mk
@@ -64,6 +64,7 @@
 	ISurfaceComposer.cpp \
 	ISurfaceComposerClient.cpp \
 	LayerState.cpp \
+	OccupancyTracker.cpp \
 	Sensor.cpp \
 	SensorEventQueue.cpp \
 	SensorManager.cpp \
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index cbc8893..e8860d1 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -260,6 +260,7 @@
         mCore->mDequeueCondition.broadcast();
 
         ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
+        mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
 
         VALIDATE_CONSISTENCY();
     }
@@ -717,6 +718,13 @@
     return mCore->mSidebandStream;
 }
 
+status_t BufferQueueConsumer::getOccupancyHistory(bool forceFlush,
+        std::vector<OccupancyTracker::Segment>* outHistory) {
+    Mutex::Autolock lock(mCore->mMutex);
+    *outHistory = mCore->mOccupancyTracker.getSegmentHistory(forceFlush);
+    return NO_ERROR;
+}
+
 void BufferQueueConsumer::dump(String8& result, const char* prefix) const {
     const IPCThreadState* ipc = IPCThreadState::self();
     const pid_t pid = ipc->getCallingPid();
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 69a408c..07cfb4f 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -890,6 +890,7 @@
                 static_cast<uint32_t>(mCore->mQueue.size()));
 
         ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
+        mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
 
         // Take a ticket for the callback functions
         callbackTicket = mNextCallbackTicket++;
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index a6a9712..84965ef 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -235,6 +235,16 @@
     return mConsumer->setDefaultBufferDataSpace(defaultDataSpace);
 }
 
+status_t ConsumerBase::getOccupancyHistory(bool forceFlush,
+        std::vector<OccupancyTracker::Segment>* outHistory) {
+    Mutex::Autolock _l(mMutex);
+    if (mAbandoned) {
+        CB_LOGE("getOccupancyHistory: ConsumerBase is abandoned!");
+        return NO_INIT;
+    }
+    return mConsumer->getOccupancyHistory(forceFlush, outHistory);
+}
+
 void ConsumerBase::dump(String8& result) const {
     dump(result, "");
 }
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index cb1ad35..7c4379f 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -51,6 +51,7 @@
     SET_CONSUMER_USAGE_BITS,
     SET_TRANSFORM_HINT,
     GET_SIDEBAND_STREAM,
+    GET_OCCUPANCY_HISTORY,
     DUMP,
 };
 
@@ -260,6 +261,31 @@
         return stream;
     }
 
+    virtual status_t getOccupancyHistory(bool forceFlush,
+            std::vector<OccupancyTracker::Segment>* outHistory) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
+        status_t error = data.writeBool(forceFlush);
+        if (error != NO_ERROR) {
+            return error;
+        }
+        error = remote()->transact(GET_OCCUPANCY_HISTORY, data,
+                &reply);
+        if (error != NO_ERROR) {
+            return error;
+        }
+        error = reply.readParcelableVector(outHistory);
+        if (error != NO_ERROR) {
+            return error;
+        }
+        status_t result = NO_ERROR;
+        error = reply.readInt32(&result);
+        if (error != NO_ERROR) {
+            return error;
+        }
+        return result;
+    }
+
     virtual void dump(String8& result, const char* prefix) const {
         Parcel data, reply;
         data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
@@ -409,6 +435,25 @@
             }
             return NO_ERROR;
         }
+        case GET_OCCUPANCY_HISTORY: {
+            CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
+            bool forceFlush = false;
+            status_t error = data.readBool(&forceFlush);
+            if (error != NO_ERROR) {
+                return error;
+            }
+            std::vector<OccupancyTracker::Segment> history;
+            status_t result = getOccupancyHistory(forceFlush, &history);
+            error = reply->writeParcelableVector(history);
+            if (error != NO_ERROR) {
+                return error;
+            }
+            error = reply->writeInt32(result);
+            if (error != NO_ERROR) {
+                return error;
+            }
+            return NO_ERROR;
+        }
         case DUMP: {
             CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
             String8 result = data.readString8();
diff --git a/libs/gui/OccupancyTracker.cpp b/libs/gui/OccupancyTracker.cpp
new file mode 100644
index 0000000..9687aaf
--- /dev/null
+++ b/libs/gui/OccupancyTracker.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 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 "OccupancyTracker"
+
+#include <gui/OccupancyTracker.h>
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <inttypes.h>
+
+namespace android {
+
+status_t OccupancyTracker::Segment::writeToParcel(Parcel* parcel) const {
+    status_t result = parcel->writeInt64(totalTime);
+    if (result != OK) {
+        return result;
+    }
+    result = parcel->writeUint64(static_cast<uint64_t>(numFrames));
+    if (result != OK) {
+        return result;
+    }
+    result = parcel->writeFloat(occupancyAverage);
+    if (result != OK) {
+        return result;
+    }
+    return parcel->writeBool(usedThirdBuffer);
+}
+
+status_t OccupancyTracker::Segment::readFromParcel(const Parcel* parcel) {
+    status_t result = parcel->readInt64(&totalTime);
+    if (result != OK) {
+        return result;
+    }
+    uint64_t uintNumFrames = 0;
+    result = parcel->readUint64(&uintNumFrames);
+    if (result != OK) {
+        return result;
+    }
+    numFrames = static_cast<size_t>(uintNumFrames);
+    result = parcel->readFloat(&occupancyAverage);
+    if (result != OK) {
+        return result;
+    }
+    return parcel->readBool(&usedThirdBuffer);
+}
+
+void OccupancyTracker::registerOccupancyChange(size_t occupancy) {
+    ATRACE_CALL();
+    nsecs_t now = systemTime();
+    nsecs_t delta = now - mLastOccupancyChangeTime;
+    if (delta > NEW_SEGMENT_DELAY) {
+        recordPendingSegment();
+    } else {
+        mPendingSegment.totalTime += delta;
+        if (mPendingSegment.mOccupancyTimes.count(mLastOccupancy)) {
+            mPendingSegment.mOccupancyTimes[mLastOccupancy] += delta;
+        } else {
+            mPendingSegment.mOccupancyTimes[mLastOccupancy] = delta;
+        }
+    }
+    if (occupancy > mLastOccupancy) {
+        ++mPendingSegment.numFrames;
+    }
+    mLastOccupancyChangeTime = now;
+    mLastOccupancy = occupancy;
+}
+
+std::vector<OccupancyTracker::Segment> OccupancyTracker::getSegmentHistory(
+        bool forceFlush) {
+    if (forceFlush) {
+        recordPendingSegment();
+    }
+    std::vector<Segment> segments(mSegmentHistory.cbegin(),
+            mSegmentHistory.cend());
+    mSegmentHistory.clear();
+    return segments;
+}
+
+void OccupancyTracker::recordPendingSegment() {
+    // Only record longer segments to get a better measurement of actual double-
+    // vs. triple-buffered time
+    if (mPendingSegment.numFrames > LONG_SEGMENT_THRESHOLD) {
+        float occupancyAverage = 0.0f;
+        bool usedThirdBuffer = false;
+        for (const auto& timePair : mPendingSegment.mOccupancyTimes) {
+            size_t occupancy = timePair.first;
+            float timeRatio = static_cast<float>(timePair.second) /
+                    mPendingSegment.totalTime;
+            occupancyAverage += timeRatio * occupancy;
+            usedThirdBuffer = usedThirdBuffer || (occupancy > 1);
+        }
+        mSegmentHistory.push_front({mPendingSegment.totalTime,
+                mPendingSegment.numFrames, occupancyAverage, usedThirdBuffer});
+        if (mSegmentHistory.size() > MAX_HISTORY_SIZE) {
+            mSegmentHistory.pop_back();
+        }
+    }
+    mPendingSegment.clear();
+}
+
+} // namespace android
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 85d63b4..210ce8c 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -34,6 +34,10 @@
 
 #include <gtest/gtest.h>
 
+#include <thread>
+
+using namespace std::chrono_literals;
+
 namespace android {
 
 class BufferQueueTest : public ::testing::Test {
@@ -850,4 +854,140 @@
             returnedBuffer->getNativeBuffer()->handle);
 }
 
+TEST_F(BufferQueueTest, TestOccupancyHistory) {
+    createBufferQueue();
+    sp<DummyConsumer> dc(new DummyConsumer);
+    ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+    IGraphicBufferProducer::QueueBufferOutput output;
+    ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+            NATIVE_WINDOW_API_CPU, false, &output));
+
+    int slot = BufferQueue::INVALID_BUFFER_SLOT;
+    sp<Fence> fence = Fence::NO_FENCE;
+    sp<GraphicBuffer> buffer = nullptr;
+    IGraphicBufferProducer::QueueBufferInput input(0ull, true,
+        HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
+        NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+    BufferItem item{};
+
+    // Preallocate, dequeue, request, and cancel 3 buffers so we don't get
+    // BUFFER_NEEDS_REALLOCATION below
+    int slots[3] = {};
+    mProducer->setMaxDequeuedBufferCount(3);
+    for (size_t i = 0; i < 3; ++i) {
+        status_t result = mProducer->dequeueBuffer(&slots[i], &fence,
+                0, 0, 0, 0);
+        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+        ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
+    }
+    for (size_t i = 0; i < 3; ++i) {
+        ASSERT_EQ(OK, mProducer->cancelBuffer(slots[i], Fence::NO_FENCE));
+    }
+
+    // Create 3 segments
+
+    // The first segment is a two-buffer segment, so we only put one buffer into
+    // the queue at a time
+    for (size_t i = 0; i < 5; ++i) {
+        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+        ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        std::this_thread::sleep_for(16ms);
+    }
+
+    // Sleep between segments
+    std::this_thread::sleep_for(500ms);
+
+    // The second segment is a double-buffer segment. It starts the same as the
+    // two-buffer segment, but then at the end, we put two buffers in the queue
+    // at the same time before draining it.
+    for (size_t i = 0; i < 5; ++i) {
+        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+        ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        std::this_thread::sleep_for(16ms);
+    }
+    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    std::this_thread::sleep_for(16ms);
+    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+
+    // Sleep between segments
+    std::this_thread::sleep_for(500ms);
+
+    // The third segment is a triple-buffer segment, so the queue is switching
+    // between one buffer and two buffers deep.
+    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+    for (size_t i = 0; i < 5; ++i) {
+        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+        ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        std::this_thread::sleep_for(16ms);
+    }
+    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+
+    // Now we read the segments
+    std::vector<OccupancyTracker::Segment> history;
+    ASSERT_EQ(OK, mConsumer->getOccupancyHistory(false, &history));
+
+    // Since we didn't force a flush, we should only get the first two segments
+    // (since the third segment hasn't been closed out by the appearance of a
+    // new segment yet)
+    ASSERT_EQ(2u, history.size());
+
+    // The first segment (which will be history[1], since the newest segment
+    // should be at the front of the vector) should be a two-buffer segment,
+    // which implies that the occupancy average should be between 0 and 1, and
+    // usedThirdBuffer should be false
+    const auto& firstSegment = history[1];
+    ASSERT_EQ(5u, firstSegment.numFrames);
+    ASSERT_LT(0, firstSegment.occupancyAverage);
+    ASSERT_GT(1, firstSegment.occupancyAverage);
+    ASSERT_EQ(false, firstSegment.usedThirdBuffer);
+
+    // The second segment should be a double-buffered segment, which implies that
+    // the occupancy average should be between 0 and 1, but usedThirdBuffer
+    // should be true
+    const auto& secondSegment = history[0];
+    ASSERT_EQ(7u, secondSegment.numFrames);
+    ASSERT_LT(0, secondSegment.occupancyAverage);
+    ASSERT_GT(1, secondSegment.occupancyAverage);
+    ASSERT_EQ(true, secondSegment.usedThirdBuffer);
+
+    // If we read the segments again without flushing, we shouldn't get any new
+    // segments
+    ASSERT_EQ(OK, mConsumer->getOccupancyHistory(false, &history));
+    ASSERT_EQ(0u, history.size());
+
+    // Read the segments again, this time forcing a flush so we get the third
+    // segment
+    ASSERT_EQ(OK, mConsumer->getOccupancyHistory(true, &history));
+    ASSERT_EQ(1u, history.size());
+
+    // This segment should be a triple-buffered segment, which implies that the
+    // occupancy average should be between 1 and 2, and usedThirdBuffer should
+    // be true
+    const auto& thirdSegment = history[0];
+    ASSERT_EQ(6u, thirdSegment.numFrames);
+    ASSERT_LT(1, thirdSegment.occupancyAverage);
+    ASSERT_GT(2, thirdSegment.occupancyAverage);
+    ASSERT_EQ(true, thirdSegment.usedThirdBuffer);
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputReader.cpp b/services/inputflinger/InputReader.cpp
index 374a5de..a2d689b 100644
--- a/services/inputflinger/InputReader.cpp
+++ b/services/inputflinger/InputReader.cpp
@@ -134,6 +134,10 @@
         { AKEYCODE_DPAD_RIGHT,  AKEYCODE_DPAD_UP,     AKEYCODE_DPAD_LEFT,   AKEYCODE_DPAD_DOWN },
         { AKEYCODE_DPAD_UP,     AKEYCODE_DPAD_LEFT,   AKEYCODE_DPAD_DOWN,   AKEYCODE_DPAD_RIGHT },
         { AKEYCODE_DPAD_LEFT,   AKEYCODE_DPAD_DOWN,   AKEYCODE_DPAD_RIGHT,  AKEYCODE_DPAD_UP },
+        { AKEYCODE_FP_NAV_DOWN,   AKEYCODE_FP_NAV_RIGHT,  AKEYCODE_FP_NAV_UP,     AKEYCODE_FP_NAV_LEFT },
+        { AKEYCODE_FP_NAV_RIGHT,  AKEYCODE_FP_NAV_UP,     AKEYCODE_FP_NAV_LEFT,   AKEYCODE_FP_NAV_DOWN },
+        { AKEYCODE_FP_NAV_UP,     AKEYCODE_FP_NAV_LEFT,   AKEYCODE_FP_NAV_DOWN,   AKEYCODE_FP_NAV_RIGHT },
+        { AKEYCODE_FP_NAV_LEFT,   AKEYCODE_FP_NAV_DOWN,   AKEYCODE_FP_NAV_RIGHT,  AKEYCODE_FP_NAV_UP },
 };
 static const size_t keyCodeRotationMapSize =
         sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index fb6307e..d654b17 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -44,7 +44,6 @@
 
 LOCAL_CFLAGS := -DLOG_TAG=\"SurfaceFlinger\"
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-#LOCAL_CFLAGS += -DENABLE_FENCE_TRACKING
 
 USE_HWC2 := false
 ifeq ($(USE_HWC2),true)
diff --git a/services/surfaceflinger/FenceTracker.cpp b/services/surfaceflinger/FenceTracker.cpp
index 885d712..d415bd5 100644
--- a/services/surfaceflinger/FenceTracker.cpp
+++ b/services/surfaceflinger/FenceTracker.cpp
@@ -184,8 +184,6 @@
 
     mOffset = (mOffset + 1) % MAX_FRAME_HISTORY;
     mFrameCounter++;
-
-    checkFencesForCompletion();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/FenceTracker.h b/services/surfaceflinger/FenceTracker.h
index de99820..2fcc314 100644
--- a/services/surfaceflinger/FenceTracker.h
+++ b/services/surfaceflinger/FenceTracker.h
@@ -42,7 +42,7 @@
              const Vector<sp<Layer>>& layers, sp<Fence> glDoneFence);
 
 protected:
-     static constexpr size_t MAX_FRAME_HISTORY = 128;
+     static constexpr size_t MAX_FRAME_HISTORY = 8;
 
      struct LayerRecord {
          String8 name; // layer name
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index dffc542..abf0e6c 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1660,7 +1660,8 @@
     return mQueuedFrames > 0 || mSidebandStreamChanged || mAutoRefresh;
 }
 
-void Layer::onPostComposition() {
+bool Layer::onPostComposition() {
+    bool frameLatencyNeeded = mFrameLatencyNeeded;
     if (mFrameLatencyNeeded) {
         nsecs_t desiredPresentTime = mSurfaceFlingerConsumer->getTimestamp();
         mFrameTracker.setDesiredPresentTime(desiredPresentTime);
@@ -1692,6 +1693,7 @@
         mFrameTracker.advanceFrame();
         mFrameLatencyNeeded = false;
     }
+    return frameLatencyNeeded;
 }
 
 #ifdef USE_HWC2
@@ -2165,6 +2167,20 @@
     *outAcquireFence = mSurfaceFlingerConsumer->getCurrentFence();
     *outPrevReleaseFence = mSurfaceFlingerConsumer->getPrevReleaseFence();
 }
+
+std::vector<OccupancyTracker::Segment> Layer::getOccupancyHistory(
+        bool forceFlush) {
+    std::vector<OccupancyTracker::Segment> history;
+    status_t result = mSurfaceFlingerConsumer->getOccupancyHistory(forceFlush,
+            &history);
+    if (result != NO_ERROR) {
+        ALOGW("[%s] Failed to obtain occupancy history (%d)", mName.string(),
+                result);
+        return {};
+    }
+    return history;
+}
+
 // ---------------------------------------------------------------------------
 
 Layer::LayerCleaner::LayerCleaner(const sp<SurfaceFlinger>& flinger,
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7d085a4..79750a6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -274,9 +274,10 @@
     bool onPreComposition();
 
     /*
-     *  called after composition.
+     * called after composition.
+     * returns true if the layer latched a new buffer this frame.
      */
-    void onPostComposition();
+    bool onPostComposition();
 
 #ifdef USE_HWC2
     // If a buffer was replaced this frame, release the former buffer
@@ -406,6 +407,8 @@
             bool* outIsGlesComposition, nsecs_t* outPostedTime,
             sp<Fence>* outAcquireFence, sp<Fence>* outPrevReleaseFence) const;
 
+    std::vector<OccupancyTracker::Segment> getOccupancyHistory(bool forceFlush);
+
 protected:
     // constant
     sp<SurfaceFlinger> mFlinger;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 80b4d75..0276d38 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -939,11 +939,7 @@
 void SurfaceFlinger::handleMessageRefresh() {
     ATRACE_CALL();
 
-#ifdef ENABLE_FENCE_TRACKING
     nsecs_t refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-#else
-    nsecs_t refreshStartTime = 0;
-#endif
     static nsecs_t previousExpectedPresent = 0;
     nsecs_t expectedPresent = mPrimaryDispSync.computeNextRefresh(0);
     static bool previousFrameMissed = false;
@@ -1033,11 +1029,7 @@
     }
 }
 
-#ifdef ENABLE_FENCE_TRACKING
 void SurfaceFlinger::postComposition(nsecs_t refreshStartTime)
-#else
-void SurfaceFlinger::postComposition(nsecs_t /*refreshStartTime*/)
-#endif
 {
     ATRACE_CALL();
     ALOGV("postComposition");
@@ -1045,7 +1037,11 @@
     const LayerVector& layers(mDrawingState.layersSortedByZ);
     const size_t count = layers.size();
     for (size_t i=0 ; i<count ; i++) {
-        layers[i]->onPostComposition();
+        bool frameLatched = layers[i]->onPostComposition();
+        if (frameLatched) {
+            recordBufferingStats(layers[i]->getName().string(),
+                    layers[i]->getOccupancyHistory(false));
+        }
     }
 
     sp<Fence> presentFence = mHwc->getRetireFence(HWC_DISPLAY_PRIMARY);
@@ -1065,10 +1061,8 @@
         }
     }
 
-#ifdef ENABLE_FENCE_TRACKING
     mFenceTracker.addFrame(refreshStartTime, presentFence,
             hw->getVisibleLayersSortedByZ(), hw->getClientTargetAcquireFence());
-#endif
 
     if (mAnimCompositionPending) {
         mAnimCompositionPending = false;
@@ -1646,6 +1640,8 @@
     if (!mLayersPendingRemoval.isEmpty()) {
         // Notify removed layers now that they can't be drawn from
         for (size_t i = 0; i < mLayersPendingRemoval.size(); i++) {
+            recordBufferingStats(mLayersPendingRemoval[i]->getName().string(),
+                    mLayersPendingRemoval[i]->getOccupancyHistory(true));
             mLayersPendingRemoval[i]->onRemoved();
         }
         mLayersPendingRemoval.clear();
@@ -2604,14 +2600,12 @@
                 dumpAll = false;
             }
 
-#ifdef ENABLE_FENCE_TRACKING
             if ((index < numArgs) &&
                     (args[index] == String16("--fences"))) {
                 index++;
                 mFenceTracker.dump(&result);
                 dumpAll = false;
             }
-#endif
         }
 
         if (dumpAll) {
@@ -2732,6 +2726,59 @@
             NUM_BUCKETS - 1, bucketTimeSec, percent);
 }
 
+void SurfaceFlinger::recordBufferingStats(const char* layerName,
+        std::vector<OccupancyTracker::Segment>&& history) {
+    Mutex::Autolock lock(mBufferingStatsMutex);
+    auto& stats = mBufferingStats[layerName];
+    for (const auto& segment : history) {
+        if (!segment.usedThirdBuffer) {
+            stats.twoBufferTime += segment.totalTime;
+        }
+        if (segment.occupancyAverage < 1.0f) {
+            stats.doubleBufferedTime += segment.totalTime;
+        } else if (segment.occupancyAverage < 2.0f) {
+            stats.tripleBufferedTime += segment.totalTime;
+        }
+        ++stats.numSegments;
+        stats.totalTime += segment.totalTime;
+    }
+}
+
+void SurfaceFlinger::dumpBufferingStats(String8& result) const {
+    result.append("Buffering stats:\n");
+    result.append("  [Layer name] <Active time> <Two buffer> "
+            "<Double buffered> <Triple buffered>\n");
+    Mutex::Autolock lock(mBufferingStatsMutex);
+    typedef std::tuple<std::string, float, float, float> BufferTuple;
+    std::map<float, BufferTuple, std::greater<float>> sorted;
+    for (const auto& statsPair : mBufferingStats) {
+        const char* name = statsPair.first.c_str();
+        const BufferingStats& stats = statsPair.second;
+        if (stats.numSegments == 0) {
+            continue;
+        }
+        float activeTime = ns2ms(stats.totalTime) / 1000.0f;
+        float twoBufferRatio = static_cast<float>(stats.twoBufferTime) /
+                stats.totalTime;
+        float doubleBufferRatio = static_cast<float>(
+                stats.doubleBufferedTime) / stats.totalTime;
+        float tripleBufferRatio = static_cast<float>(
+                stats.tripleBufferedTime) / stats.totalTime;
+        sorted.insert({activeTime, {name, twoBufferRatio,
+                doubleBufferRatio, tripleBufferRatio}});
+    }
+    for (const auto& sortedPair : sorted) {
+        float activeTime = sortedPair.first;
+        const BufferTuple& values = sortedPair.second;
+        result.appendFormat("  [%s] %.2f %.3f %.3f %.3f\n",
+                std::get<0>(values).c_str(), activeTime,
+                std::get<1>(values), std::get<2>(values),
+                std::get<3>(values));
+    }
+    result.append("\n");
+}
+
+
 void SurfaceFlinger::dumpAllLocked(const Vector<String16>& args, size_t& index,
         String8& result) const
 {
@@ -2785,6 +2832,8 @@
     dumpStaticScreenStats(result);
     result.append("\n");
 
+    dumpBufferingStats(result);
+
     /*
      * Dump the visible layer list
      */
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 633e956..8263994 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -42,6 +42,7 @@
 
 #include <gui/ISurfaceComposer.h>
 #include <gui/ISurfaceComposerClient.h>
+#include <gui/OccupancyTracker.h>
 
 #include <hardware/hwcomposer_defs.h>
 
@@ -57,6 +58,9 @@
 #include "DisplayHardware/HWComposer.h"
 #include "Effects/Daltonizer.h"
 
+#include <map>
+#include <string>
+
 namespace android {
 
 // ---------------------------------------------------------------------------
@@ -432,6 +436,10 @@
 
     void dumpStaticScreenStats(String8& result) const;
 
+    void recordBufferingStats(const char* layerName,
+            std::vector<OccupancyTracker::Segment>&& history);
+    void dumpBufferingStats(String8& result) const;
+
     /* ------------------------------------------------------------------------
      * Attributes
      */
@@ -526,6 +534,29 @@
     nsecs_t mFrameBuckets[NUM_BUCKETS];
     nsecs_t mTotalTime;
     std::atomic<nsecs_t> mLastSwapTime;
+
+    // Double- vs. triple-buffering stats
+    struct BufferingStats {
+        BufferingStats()
+          : numSegments(0),
+            totalTime(0),
+            twoBufferTime(0),
+            doubleBufferedTime(0),
+            tripleBufferedTime(0) {}
+
+        size_t numSegments;
+        nsecs_t totalTime;
+
+        // "Two buffer" means that a third buffer was never used, whereas
+        // "double-buffered" means that on average the segment only used two
+        // buffers (though it may have used a third for some part of the
+        // segment)
+        nsecs_t twoBufferTime;
+        nsecs_t doubleBufferedTime;
+        nsecs_t tripleBufferedTime;
+    };
+    mutable Mutex mBufferingStatsMutex;
+    std::unordered_map<std::string, BufferingStats> mBufferingStats;
 };
 
 }; // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
index 5721db7..71d7cf9 100644
--- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
+++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
@@ -943,11 +943,7 @@
 void SurfaceFlinger::handleMessageRefresh() {
     ATRACE_CALL();
 
-#ifdef ENABLE_FENCE_TRACKING
     nsecs_t refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-#else
-    nsecs_t refreshStartTime = 0;
-#endif
     static nsecs_t previousExpectedPresent = 0;
     nsecs_t expectedPresent = mPrimaryDispSync.computeNextRefresh(0);
     static bool previousFrameMissed = false;
@@ -1029,16 +1025,16 @@
     }
 }
 
-#ifdef ENABLE_FENCE_TRACKING
 void SurfaceFlinger::postComposition(nsecs_t refreshStartTime)
-#else
-void SurfaceFlinger::postComposition(nsecs_t /*refreshStartTime*/)
-#endif
 {
     const LayerVector& layers(mDrawingState.layersSortedByZ);
     const size_t count = layers.size();
     for (size_t i=0 ; i<count ; i++) {
-        layers[i]->onPostComposition();
+        bool frameLatched = layers[i]->onPostComposition();
+        if (frameLatched) {
+            recordBufferingStats(layers[i]->getName().string(),
+                    layers[i]->getOccupancyHistory(false));
+        }
     }
 
     const HWComposer& hwc = getHwComposer();
@@ -1059,10 +1055,8 @@
         }
     }
 
-#ifdef ENABLE_FENCE_TRACKING
     mFenceTracker.addFrame(refreshStartTime, presentFence,
             hw->getVisibleLayersSortedByZ(), hw->getClientTargetAcquireFence());
-#endif
 
     if (mAnimCompositionPending) {
         mAnimCompositionPending = false;
@@ -1666,6 +1660,8 @@
     if (!mLayersPendingRemoval.isEmpty()) {
         // Notify removed layers now that they can't be drawn from
         for (size_t i = 0; i < mLayersPendingRemoval.size(); i++) {
+            recordBufferingStats(mLayersPendingRemoval[i]->getName().string(),
+                    mLayersPendingRemoval[i]->getOccupancyHistory(true));
             mLayersPendingRemoval[i]->onRemoved();
         }
         mLayersPendingRemoval.clear();
@@ -2620,14 +2616,12 @@
                 dumpAll = false;
             }
 
-#ifdef ENABLE_FENCE_TRACKING
             if ((index < numArgs) &&
                     (args[index] == String16("--fences"))) {
                 index++;
                 mFenceTracker.dump(&result);
                 dumpAll = false;
             }
-#endif
         }
 
         if (dumpAll) {
@@ -2748,6 +2742,58 @@
             NUM_BUCKETS - 1, bucketTimeSec, percent);
 }
 
+void SurfaceFlinger::recordBufferingStats(const char* layerName,
+        std::vector<OccupancyTracker::Segment>&& history) {
+    Mutex::Autolock lock(mBufferingStatsMutex);
+    auto& stats = mBufferingStats[layerName];
+    for (const auto& segment : history) {
+        if (!segment.usedThirdBuffer) {
+            stats.twoBufferTime += segment.totalTime;
+        }
+        if (segment.occupancyAverage < 1.0f) {
+            stats.doubleBufferedTime += segment.totalTime;
+        } else if (segment.occupancyAverage < 2.0f) {
+            stats.tripleBufferedTime += segment.totalTime;
+        }
+        ++stats.numSegments;
+        stats.totalTime += segment.totalTime;
+    }
+}
+
+void SurfaceFlinger::dumpBufferingStats(String8& result) const {
+    result.append("Buffering stats:\n");
+    result.append("  [Layer name] <Active time> <Two buffer> "
+            "<Double buffered> <Triple buffered>\n");
+    Mutex::Autolock lock(mBufferingStatsMutex);
+    typedef std::tuple<std::string, float, float, float> BufferTuple;
+    std::map<float, BufferTuple, std::greater<float>> sorted;
+    for (const auto& statsPair : mBufferingStats) {
+        const char* name = statsPair.first.c_str();
+        const BufferingStats& stats = statsPair.second;
+        if (stats.numSegments == 0) {
+            continue;
+        }
+        float activeTime = ns2ms(stats.totalTime) / 1000.0f;
+        float twoBufferRatio = static_cast<float>(stats.twoBufferTime) /
+                stats.totalTime;
+        float doubleBufferRatio = static_cast<float>(
+                stats.doubleBufferedTime) / stats.totalTime;
+        float tripleBufferRatio = static_cast<float>(
+                stats.tripleBufferedTime) / stats.totalTime;
+        sorted.insert({activeTime, {name, twoBufferRatio,
+                doubleBufferRatio, tripleBufferRatio}});
+    }
+    for (const auto& sortedPair : sorted) {
+        float activeTime = sortedPair.first;
+        const BufferTuple& values = sortedPair.second;
+        result.appendFormat("  [%s] %.2f %.3f %.3f %.3f\n",
+                std::get<0>(values).c_str(), activeTime,
+                std::get<1>(values), std::get<2>(values),
+                std::get<3>(values));
+    }
+    result.append("\n");
+}
+
 void SurfaceFlinger::dumpAllLocked(const Vector<String16>& args, size_t& index,
         String8& result) const
 {
@@ -2799,6 +2845,8 @@
     dumpStaticScreenStats(result);
     result.append("\n");
 
+    dumpBufferingStats(result);
+
     /*
      * Dump the visible layer list
      */
diff --git a/services/surfaceflinger/surfaceflinger.rc b/services/surfaceflinger/surfaceflinger.rc
index 2b4ea2a..435aa0c 100644
--- a/services/surfaceflinger/surfaceflinger.rc
+++ b/services/surfaceflinger/surfaceflinger.rc
@@ -3,4 +3,4 @@
     user system
     group graphics drmrpc readproc
     onrestart restart zygote
-    writepid /sys/fs/cgroup/stune/foreground/tasks
+    writepid /dev/stune/foreground/tasks