Merge "Add window manager tracing"
diff --git a/Android.bp b/Android.bp
index c89cc40..5c1ccb7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,6 +47,7 @@
             srcs: [
                 "core/proto/**/*.proto",
                 "libs/incident/**/*.proto",
+                "tools/streaming_proto/stream.proto",
             ],
         },
         android: {
@@ -61,7 +62,7 @@
                 "core/proto/android/os/pagetypeinfo.proto",
                 "core/proto/android/os/procrank.proto",
                 "core/proto/android/service/graphicsstats.proto",
-                "libs/incident/proto/android/privacy.proto",
+                "tools/streaming_proto/stream.proto",
             ],
             shared: {
                 enabled: false,
@@ -70,6 +71,27 @@
     },
 }
 
+gensrcs {
+    name: "gen-platform-proto-constants",
+    depfile: true,
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-cppstream",
+    ],
+
+    srcs: [
+        "core/proto/android/os/kernelwake.proto",
+        "core/proto/android/os/pagetypeinfo.proto",
+        "core/proto/android/os/procrank.proto",
+    ],
+
+    // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
+    cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)",
+
+    output_extension = "proto.h",
+}
+
 subdirs = [
     "cmds/*",
     "core/*",
diff --git a/Android.mk b/Android.mk
index 9843f17..1ed8a25 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1553,6 +1553,7 @@
     -Iexternal/protobuf/src
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 LOCAL_SRC_FILES := \
+    tools/streaming_proto/stream.proto \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto)
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 0532083..2ef0371 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -8,44 +8,53 @@
         "-O0"
     ],
 
-    srcs: [
-        "IncidentHelper.cpp",
-        "ih_util.cpp",
+    local_include_dirs: [
+        "src/",
+        "src/parsers/",
     ],
 
+    srcs: [
+        "src/parsers/*.cpp",
+        "src/TextParserBase.cpp",
+        "src/ih_util.cpp",
+    ],
+
+    generated_headers: ["gen-platform-proto-constants"],
+
     shared_libs: [
         "libbase",
         "liblog",
-        "libprotobuf-cpp-full",
+        "libprotoutil",
         "libutils",
     ],
-
-    static_libs: [
-        "libplatformprotos",
-    ],
 }
 
 cc_binary {
     name: "incident_helper",
     defaults: ["incident_helper_defaults"],
-    srcs: ["main.cpp"],
+    srcs: ["src/main.cpp"],
 }
 
 
 cc_test {
     name: "incident_helper_test",
     defaults: ["incident_helper_defaults"],
+    local_include_dirs: ["src/"],
 
     srcs: [
-        "tests/IncidentHelper_test.cpp",
-        "tests/ih_util_test.cpp",
+        "tests/*.cpp",
     ],
 
     data: [
         "testdata/*",
     ],
 
+    shared_libs: [
+        "libprotobuf-cpp-full",
+    ],
+
     static_libs: [
         "libgmock",
+        "libplatformprotos"
     ],
-}
\ No newline at end of file
+}
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
deleted file mode 100644
index 7b06d42..0000000
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "incident_helper"
-
-#include "IncidentHelper.h"
-#include "ih_util.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
-
-using namespace android::base;
-using namespace android::os;
-using namespace google::protobuf;
-using namespace std;
-
-
-static const string TAB_DELIMITER = "\t";
-static const string COMMA_DELIMITER = ",";
-
-static inline int toInt(const string& s) {
-    return atoi(s.c_str());
-}
-
-static inline long toLong(const string& s) {
-    return atol(s.c_str());
-}
-
-/**
- * Sets the given protobuf message when the field name matches one of the
- * fields. It is useful to set values to proto from table-like plain texts.
- */
-static bool
-SetTableField(::google::protobuf::Message* message, string field_name, string field_value) {
-    const Descriptor* descriptor = message->GetDescriptor();
-    const Reflection* reflection = message->GetReflection();
-
-    const FieldDescriptor* field = descriptor->FindFieldByName(field_name);
-    switch (field->type()) {
-        case FieldDescriptor::TYPE_STRING:
-            reflection->SetString(message, field, field_value);
-            return true;
-        case FieldDescriptor::TYPE_INT64:
-            reflection->SetInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT64:
-            reflection->SetUInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_INT32:
-            reflection->SetInt32(message, field, toInt(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT32:
-            reflection->SetUInt32(message, field, toInt(field_value));
-            return true;
-        default:
-            // Add new scalar types
-            return false;
-    }
-}
-
-// ================================================================================
-status_t NoopParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t ReverseParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    // reverse the content
-    reverse(content.begin(), content.end());
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t KernelWakesParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    header_t header;  // the header of /d/wakeup_sources
-    record_t record;  // retain each record
-    int nline = 0;
-
-    KernelWakeSources proto;
-
-    // parse line by line
-    while (reader.readLine(&line)) {
-        if (line.empty()) continue;
-        // parse head line
-        if (nline++ == 0) {
-            header = parseHeader(line, TAB_DELIMITER);
-            continue;
-        }
-
-        // parse for each record, the line delimiter is \t only!
-        record = parseRecord(line, TAB_DELIMITER);
-
-        if (record.size() != header.size()) {
-            // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
-            continue;
-        }
-
-        WakeupSourceProto* source = proto.add_wakeup_sources();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(source, header[i], record[i])) {
-                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
-            }
-        }
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!proto.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t ProcrankParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    header_t header;  // the header of /d/wakeup_sources
-    record_t record;  // retain each record
-    int nline = 0;
-
-    Procrank proto;
-
-    // parse line by line
-    while (reader.readLine(&line)) {
-        if (line.empty()) continue;
-
-        // parse head line
-        if (nline++ == 0) {
-            header = parseHeader(line);
-            continue;
-        }
-
-        if (hasPrefix(&line, "ZRAM:")) {
-            proto.mutable_summary()->mutable_zram()->set_raw_text(line);
-            continue;
-        }
-        if (hasPrefix(&line, "RAM:")) {
-            proto.mutable_summary()->mutable_ram()->set_raw_text(line);
-            continue;
-        }
-
-        record = parseRecord(line);
-        if (record.size() != header.size()) {
-            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
-                ProcessProto* total = proto.mutable_summary()->mutable_total();
-                for (int i=1; i<=(int)record.size(); i++) {
-                    SetTableField(total, header[header.size() - i], record[record.size() - i]);
-                }
-            } else {
-                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
-                    line.c_str());
-            }
-            continue;
-        }
-
-        ProcessProto* process = proto.add_processes();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(process, header[i], record[i])) {
-                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
-            }
-        }
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!proto.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t PageTypeInfoParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    bool migrateTypeSession = false;
-    int pageBlockOrder;
-    header_t blockHeader;
-
-    PageTypeInfo pageTypeInfo;
-
-    while (reader.readLine(&line)) {
-        if (line.empty()) {
-            migrateTypeSession = false;
-            blockHeader.clear();
-            continue;
-        }
-
-        if (hasPrefix(&line, "Page block order:")) {
-            pageBlockOrder = toInt(line);
-            pageTypeInfo.set_page_block_order(pageBlockOrder);
-            continue;
-        }
-        if (hasPrefix(&line, "Pages per block:")) {
-            pageTypeInfo.set_pages_per_block(toInt(line));
-            continue;
-        }
-        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
-            migrateTypeSession = true;
-            continue;
-        }
-        if (hasPrefix(&line, "Number of blocks type")) {
-            blockHeader = parseHeader(line);
-            continue;
-        }
-
-        record_t record = parseRecord(line, COMMA_DELIMITER);
-        if (migrateTypeSession && record.size() == 3) {
-            MigrateTypeProto* migrateType = pageTypeInfo.add_migrate_types();
-            // expect part 0 starts with "Node"
-            if (hasPrefix(&record[0], "Node")) {
-                migrateType->set_node(toInt(record[0]));
-            } else goto ERROR;
-            // expect part 1 starts with "zone"
-            if (hasPrefix(&record[1], "zone")) {
-                migrateType->set_zone(record[1]);
-            } else goto ERROR;
-            // expect part 2 starts with "type"
-            if (hasPrefix(&record[2], "type")) {
-                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
-                // An example looks like:
-                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
-                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
-                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
-                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
-                record_t pageCounts = parseRecord(record[2]);
-                int pageCountsSize = pageBlockOrder + 2;
-                if ((int)pageCounts.size() != pageCountsSize) goto ERROR;
-
-                migrateType->set_type(pageCounts[0]);
-                for (auto i=1; i<pageCountsSize; i++) {
-                    migrateType->add_free_pages_count(toInt(pageCounts[i]));
-                }
-            } else goto ERROR;
-            continue;
-        }
-
-        if (!blockHeader.empty() && record.size() == 2) {
-            BlockProto* block = pageTypeInfo.add_blocks();
-
-            if (hasPrefix(&record[0], "Node")) {
-                block->set_node(toInt(record[0]));
-            } else goto ERROR;
-
-            if (hasPrefix(&record[1], "zone")) {
-                record_t blockCounts = parseRecord(record[1]);
-                block->set_zone(blockCounts[0]);
-                for (size_t i=0; i<blockHeader.size(); i++) {
-                    if (!SetTableField(block, blockHeader[i], blockCounts[i+1])) goto ERROR;
-                }
-            } else goto ERROR;
-
-            continue;
-        }
-
-ERROR:  // print out error for this single line and continue parsing
-        fprintf(stderr, "[%s]Bad line: %s\n", this->name.string(), line.c_str());
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!pageTypeInfo.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), pageTypeInfo.ByteSize());
-    return NO_ERROR;
-}
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
new file mode 100644
index 0000000..a8f9968
--- /dev/null
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "incident_helper"
+
+#include "TextParserBase.h"
+
+#include <android-base/file.h>
+
+using namespace android::base;
+using namespace std;
+
+// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
+
+// ================================================================================
+status_t ReverseParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    // reverse the content
+    reverse(content.begin(), content.end());
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/src/TextParserBase.h
similarity index 64%
rename from cmds/incident_helper/IncidentHelper.h
rename to cmds/incident_helper/src/TextParserBase.h
index d24d717..c41612d 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/src/TextParserBase.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCIDENT_HELPER_H
-#define INCIDENT_HELPER_H
+#ifndef TEXT_PARSER_BASE_H
+#define TEXT_PARSER_BASE_H
 
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -68,37 +68,4 @@
     virtual status_t Parse(const int in, const int out) const;
 };
 
-/**
- * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
- */
-class KernelWakesParser : public TextParserBase {
-public:
-    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
-    ~KernelWakesParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
- */
-class PageTypeInfoParser : public TextParserBase {
-public:
-    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
-    ~PageTypeInfoParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * Procrank parser, parses text produced by command procrank
- */
-class ProcrankParser : public TextParserBase {
-public:
-    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
-    ~ProcrankParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-#endif  // INCIDENT_HELPER_H
+#endif // TEXT_PARSER_BASE_H
\ No newline at end of file
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
similarity index 73%
rename from cmds/incident_helper/ih_util.cpp
rename to cmds/incident_helper/src/ih_util.cpp
index 2ab4b54..c7d1ca2 100644
--- a/cmds/incident_helper/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -87,6 +87,15 @@
     return true;
 }
 
+int toInt(const std::string& s) {
+    return atoi(s.c_str());
+}
+
+long long toLongLong(const std::string& s) {
+    return atoll(s.c_str());
+}
+
+// ==============================================================================
 Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
 
 Reader::Reader(const int fd, const size_t capacity)
@@ -151,3 +160,57 @@
     error->assign(mStatus);
     return mStatus.empty();
 }
+
+// ==============================================================================
+Table::Table(const char* names[], const uint64_t ids[], const int count)
+        :mFieldNames(names),
+         mFieldIds(ids),
+         mFieldCount(count)
+{
+}
+
+Table::~Table()
+{
+}
+
+bool
+Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+{
+    uint64_t found = 0;
+    for (int i=0; i<mFieldCount; i++) {
+        if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
+            found = mFieldIds[i];
+            break;
+        }
+    }
+
+    switch (found & FIELD_TYPE_MASK) {
+        case FIELD_TYPE_DOUBLE:
+        case FIELD_TYPE_FLOAT:
+            // TODO: support parse string to float/double
+            return false;
+        case FIELD_TYPE_STRING:
+        case FIELD_TYPE_BYTES:
+            proto.write(found, value);
+            break;
+        case FIELD_TYPE_INT64:
+        case FIELD_TYPE_SINT64:
+        case FIELD_TYPE_UINT64:
+        case FIELD_TYPE_FIXED64:
+        case FIELD_TYPE_SFIXED64:
+            proto.write(found, toLongLong(value));
+            break;
+        case FIELD_TYPE_BOOL:
+        case FIELD_TYPE_ENUM:
+        case FIELD_TYPE_INT32:
+        case FIELD_TYPE_SINT32:
+        case FIELD_TYPE_UINT32:
+        case FIELD_TYPE_FIXED32:
+        case FIELD_TYPE_SFIXED32:
+            proto.write(found, toInt(value));
+            break;
+        default:
+            return false;
+    }
+    return true;
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/src/ih_util.h
similarity index 79%
rename from cmds/incident_helper/ih_util.h
rename to cmds/incident_helper/src/ih_util.h
index ce5baee..86761e9 100644
--- a/cmds/incident_helper/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -21,6 +21,10 @@
 #include <vector>
 #include <sstream>
 
+#include <android/util/ProtoOutputStream.h>
+
+using namespace android::util;
+
 typedef std::vector<std::string> header_t;
 typedef std::vector<std::string> record_t;
 typedef std::string (*trans_func) (const std::string&);
@@ -52,6 +56,12 @@
 bool hasPrefix(std::string* line, const char* key);
 
 /**
+ * Converts string to the desired type
+ */
+int toInt(const std::string& s);
+long long toLongLong(const std::string& s);
+
+/**
  * Reader class reads data from given fd in streaming fashion.
  * The buffer size is controlled by capacity parameter.
  */
@@ -78,4 +88,22 @@
     inline bool EOR() { return mFd == -1 && mBufSize == 0; };
 };
 
+/**
+ * The class contains a mapping between table headers to its field ids.
+ * And allow users to insert the field values to proto based on its header name.
+ */
+class Table
+{
+public:
+    Table(const char* names[], const uint64_t ids[], const int count);
+    ~Table();
+
+    bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+
+private:
+    const char** mFieldNames;
+    const uint64_t* mFieldIds;
+    const int mFieldCount;
+};
+
 #endif  // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/src/main.cpp
similarity index 96%
rename from cmds/incident_helper/main.cpp
rename to cmds/incident_helper/src/main.cpp
index 52ff777..3da87b9c 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,7 +16,9 @@
 
 #define LOG_TAG "incident_helper"
 
-#include "IncidentHelper.h"
+#include "parsers/KernelWakesParser.h"
+#include "parsers/PageTypeInfoParser.h"
+#include "parsers/ProcrankParser.h"
 
 #include <android-base/file.h>
 #include <getopt.h>
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
new file mode 100644
index 0000000..cc4a1e1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/kernelwake.proto.h"
+#include "ih_util.h"
+#include "KernelWakesParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = "\t";
+
+status_t
+KernelWakesParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(WakeupSourceProto::_FIELD_NAMES, WakeupSourceProto::_FIELD_IDS, WakeupSourceProto::_FIELD_COUNT);
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line, LINE_DELIMITER);
+            continue;
+        }
+
+        // parse for each record, the line delimiter is \t only!
+        record = parseRecord(line, LINE_DELIMITER);
+
+        if (record.size() != header.size()) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
+            continue;
+        }
+
+        long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.h b/cmds/incident_helper/src/parsers/KernelWakesParser.h
new file mode 100644
index 0000000..aabab7c
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KERNEL_WAKES_PARSER_H
+#define KERNEL_WAKES_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
+ */
+class KernelWakesParser : public TextParserBase {
+public:
+    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
+    ~KernelWakesParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // KERNEL_WAKES_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
new file mode 100644
index 0000000..6047bd1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.proto.h"
+#include "ih_util.h"
+#include "PageTypeInfoParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = ",";
+
+status_t
+PageTypeInfoParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    bool migrateTypeSession = false;
+    int pageBlockOrder;
+    header_t blockHeader;
+
+    ProtoOutputStream proto;
+    Table table(BlockProto::_FIELD_NAMES, BlockProto::_FIELD_IDS, BlockProto::_FIELD_COUNT);
+
+    while (reader.readLine(&line)) {
+        if (line.empty()) {
+            migrateTypeSession = false;
+            blockHeader.clear();
+            continue;
+        }
+
+        if (hasPrefix(&line, "Page block order:")) {
+            pageBlockOrder = toInt(line);
+            proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
+            continue;
+        }
+        if (hasPrefix(&line, "Pages per block:")) {
+            proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
+            continue;
+        }
+        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+            migrateTypeSession = true;
+            continue;
+        }
+        if (hasPrefix(&line, "Number of blocks type")) {
+            blockHeader = parseHeader(line);
+            continue;
+        }
+
+        record_t record = parseRecord(line, LINE_DELIMITER);
+        if (migrateTypeSession && record.size() == 3) {
+            long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
+            // expect part 0 starts with "Node"
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(MigrateTypeProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+            // expect part 1 starts with "zone"
+            if (hasPrefix(&record[1], "zone")) {
+                proto.write(MigrateTypeProto::ZONE, record[1]);
+            } else return BAD_VALUE;
+            // expect part 2 starts with "type"
+            if (hasPrefix(&record[2], "type")) {
+                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
+                // An example looks like:
+                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
+                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
+                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
+                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
+                record_t pageCounts = parseRecord(record[2]);
+                int pageCountsSize = pageBlockOrder + 2;
+                if ((int)pageCounts.size() != pageCountsSize) return BAD_VALUE;
+
+                proto.write(MigrateTypeProto::TYPE, pageCounts[0]);
+                for (auto i=1; i<pageCountsSize; i++) {
+                    proto.write(MigrateTypeProto::FREE_PAGES_COUNT, toInt(pageCounts[i]));
+                }
+            } else return BAD_VALUE;
+
+            proto.end(token);
+        } else if (!blockHeader.empty() && record.size() == 2) {
+            long long token = proto.start(PageTypeInfo::BLOCKS);
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(BlockProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+
+            if (hasPrefix(&record[1], "zone")) {
+                record_t blockCounts = parseRecord(record[1]);
+                proto.write(BlockProto::ZONE, blockCounts[0]);
+
+                for (size_t i=0; i<blockHeader.size(); i++) {
+                    if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+                        return BAD_VALUE;
+                    }
+                }
+            } else return BAD_VALUE;
+            proto.end(token);
+        }
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.h b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
new file mode 100644
index 0000000..fb84d91
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PAGE_TYPE_INFO_PARSER_H
+#define PAGE_TYPE_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
+ */
+class PageTypeInfoParser : public TextParserBase {
+public:
+    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
+    ~PageTypeInfoParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PAGE_TYPE_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
new file mode 100644
index 0000000..93f970f
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/procrank.proto.h"
+#include "ih_util.h"
+#include "ProcrankParser.h"
+
+using namespace android::os;
+
+status_t
+ProcrankParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(ProcessProto::_FIELD_NAMES, ProcessProto::_FIELD_IDS, ProcessProto::_FIELD_COUNT);
+    string zram, ram, total;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line);
+            continue;
+        }
+
+        if (hasPrefix(&line, "ZRAM:")) {
+            zram = line;
+            continue;
+        }
+        if (hasPrefix(&line, "RAM:")) {
+            ram = line;
+            continue;
+        }
+
+        record = parseRecord(line);
+        if (record.size() != header.size()) {
+            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+                total = line;
+            } else {
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                    line.c_str());
+            }
+            continue;
+        }
+
+        long long token = proto.start(Procrank::PROCESSES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    // add summary
+    long long token = proto.start(Procrank::SUMMARY);
+    if (!total.empty()) {
+        record = parseRecord(total);
+        long long token = proto.start(SummaryProto::TOTAL);
+        for (int i=(int)record.size(); i>0; i--) {
+            table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+        }
+        proto.end(token);
+    }
+    if (!zram.empty()) {
+        long long token = proto.start(SummaryProto::ZRAM);
+        proto.write(ZramProto::RAW_TEXT, zram);
+        proto.end(token);
+    }
+    if (!ram.empty()) {
+        long long token = proto.start(SummaryProto::RAM);
+        proto.write(RamProto::RAW_TEXT, ram);
+        proto.end(token);
+    }
+    proto.end(token);
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.h b/cmds/incident_helper/src/parsers/ProcrankParser.h
new file mode 100644
index 0000000..5d0ee48
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PROCRANK_PARSER_H
+#define PROCRANK_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+class ProcrankParser : public TextParserBase {
+public:
+    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+    ~ProcrankParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PROCRANK_PARSER_H
diff --git a/cmds/incident_helper/testdata/kernel_wakeups_short.txt b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
new file mode 100644
index 0000000..a51926e
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
@@ -0,0 +1,3 @@
+name	active_count	last_change
+ab	8	123456123456
+df	143	0
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
deleted file mode 100644
index c44a163..0000000
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "IncidentHelper.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
-#include <google/protobuf/message.h>
-#include <gtest/gtest.h>
-#include <string.h>
-#include <fcntl.h>
-
-using namespace android::base;
-using namespace android::os;
-using namespace std;
-using ::testing::StrEq;
-using ::testing::Test;
-using ::testing::internal::CaptureStderr;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStderr;
-using ::testing::internal::GetCapturedStdout;
-
-class IncidentHelperTest : public Test {
-public:
-    virtual void SetUp() override {
-        ASSERT_TRUE(tf.fd != -1);
-    }
-
-    string getSerializedString(::google::protobuf::Message& message) {
-        string expectedStr;
-        message.SerializeToFileDescriptor(tf.fd);
-        ReadFileToString(tf.path, &expectedStr);
-        return expectedStr;
-    }
-
-protected:
-    TemporaryFile tf;
-
-    const string kTestPath = GetExecutableDirectory();
-    const string kTestDataPath = kTestPath + "/testdata/";
-};
-
-TEST_F(IncidentHelperTest, ReverseParser) {
-    ReverseParser parser;
-    TemporaryFile tf;
-
-    ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile("TestData", tf.path, false));
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(tf.fd, STDOUT_FILENO));
-    EXPECT_THAT(GetCapturedStdout(), StrEq("ataDtseT"));
-}
-
-TEST_F(IncidentHelperTest, KernelWakesParser) {
-    const string testFile = kTestDataPath + "kernel_wakeups.txt";
-    KernelWakesParser parser;
-    KernelWakeSources expected;
-
-    WakeupSourceProto* record1 = expected.add_wakeup_sources();
-    record1->set_name("ipc000000ab_ATFWD-daemon");
-    record1->set_active_count(8);
-    record1->set_event_count(8);
-    record1->set_wakeup_count(0);
-    record1->set_expire_count(0);
-    record1->set_active_since(0l);
-    record1->set_total_time(0l);
-    record1->set_max_time(0l);
-    record1->set_last_change(131348l);
-    record1->set_prevent_suspend_time(0l);
-
-    WakeupSourceProto* record2 = expected.add_wakeup_sources();
-    record2->set_name("ipc000000aa_ATFWD-daemon");
-    record2->set_active_count(143);
-    record2->set_event_count(143);
-    record2->set_wakeup_count(0);
-    record2->set_expire_count(0);
-    record2->set_active_since(0l);
-    record2->set_total_time(123l);
-    record2->set_max_time(3l);
-    record2->set_last_change(2067286206l);
-    record2->set_prevent_suspend_time(0l);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParser) {
-    const string testFile = kTestDataPath + "procrank.txt";
-    ProcrankParser parser;
-    Procrank expected;
-
-    ProcessProto* process1 = expected.add_processes();
-    process1->set_pid(1119);
-    process1->set_vss(2607640);
-    process1->set_rss(339564);
-    process1->set_pss(180278);
-    process1->set_uss(114216);
-    process1->set_swap(1584);
-    process1->set_pswap(46);
-    process1->set_uswap(0);
-    process1->set_zswap(10);
-    process1->set_cmdline("system_server");
-
-    ProcessProto* process2 = expected.add_processes();
-    process2->set_pid(649);
-    process2->set_vss(11016);
-    process2->set_rss(1448);
-    process2->set_pss(98);
-    process2->set_uss(48);
-    process2->set_swap(472);
-    process2->set_pswap(342);
-    process2->set_uswap(212);
-    process2->set_zswap(75);
-    process2->set_cmdline("/vendor/bin/qseecomd");
-
-    ProcessProto* total = expected.mutable_summary()->mutable_total();
-    total->set_pss(1201993);
-    total->set_uss(935300);
-    total->set_swap(88164);
-    total->set_pswap(31069);
-    total->set_uswap(27612);
-    total->set_zswap(6826);
-    total->set_cmdline("TOTAL");
-
-    expected.mutable_summary()->mutable_zram()
-        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
-    expected.mutable_summary()->mutable_ram()
-        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
-    const string testFile = kTestDataPath + "procrank_short.txt";
-    ProcrankParser parser;
-    Procrank expected;
-
-    ProcessProto* process1 = expected.add_processes();
-    process1->set_pid(1119);
-    process1->set_vss(2607640);
-    process1->set_rss(339564);
-    process1->set_pss(180278);
-    process1->set_uss(114216);
-    process1->set_cmdline("system_server");
-
-    ProcessProto* process2 = expected.add_processes();
-    process2->set_pid(649);
-    process2->set_vss(11016);
-    process2->set_rss(1448);
-    process2->set_pss(98);
-    process2->set_uss(48);
-    process2->set_cmdline("/vendor/bin/qseecomd");
-
-    ProcessProto* total = expected.mutable_summary()->mutable_total();
-    total->set_pss(1201993);
-    total->set_uss(935300);
-    total->set_cmdline("TOTAL");
-
-    expected.mutable_summary()->mutable_ram()
-        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, PageTypeInfoParser) {
-    const string testFile = kTestDataPath + "pagetypeinfo.txt";
-    PageTypeInfoParser parser;
-    PageTypeInfo expected;
-
-    expected.set_page_block_order(10);
-    expected.set_pages_per_block(1024);
-
-    MigrateTypeProto* mt1 = expected.add_migrate_types();
-    mt1->set_node(0);
-    mt1->set_zone("DMA");
-    mt1->set_type("Unmovable");
-    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
-    for (auto i=0; i<11; i++) {
-        mt1->add_free_pages_count(arr1[i]);
-    }
-
-    MigrateTypeProto* mt2 = expected.add_migrate_types();
-    mt2->set_node(0);
-    mt2->set_zone("Normal");
-    mt2->set_type("Reclaimable");
-    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
-    for (auto i=0; i<11; i++) {
-        mt2->add_free_pages_count(arr2[i]);
-    }
-
-    BlockProto* block1 = expected.add_blocks();
-    block1->set_node(0);
-    block1->set_zone("DMA");
-    block1->set_unmovable(74);
-    block1->set_reclaimable(9);
-    block1->set_movable(337);
-    block1->set_cma(41);
-    block1->set_reserve(1);
-    block1->set_isolate(0);
-
-
-    BlockProto* block2 = expected.add_blocks();
-    block2->set_node(0);
-    block2->set_zone("Normal");
-    block2->set_unmovable(70);
-    block2->set_reclaimable(12);
-    block2->set_movable(423);
-    block2->set_cma(0);
-    block2->set_reserve(1);
-    block2->set_isolate(0);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/KernelWakesParser_test.cpp b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
new file mode 100644
index 0000000..a8fa6208
--- /dev/null
+++ b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "KernelWakesParser.h"
+
+#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class KernelWakesParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(KernelWakesParserTest, Short) {
+    const string testFile = kTestDataPath + "kernel_wakeups_short.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ab");
+    record1->set_active_count(8);
+    record1->set_last_change(123456123456LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("df");
+    record2->set_active_count(143);
+    record2->set_last_change(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(KernelWakesParserTest, Normal) {
+    const string testFile = kTestDataPath + "kernel_wakeups.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ipc000000ab_ATFWD-daemon");
+    record1->set_active_count(8);
+    record1->set_event_count(8);
+    record1->set_wakeup_count(0);
+    record1->set_expire_count(0);
+    record1->set_active_since(0l);
+    record1->set_total_time(0l);
+    record1->set_max_time(0l);
+    record1->set_last_change(131348LL);
+    record1->set_prevent_suspend_time(0LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("ipc000000aa_ATFWD-daemon");
+    record2->set_active_count(143);
+    record2->set_event_count(143);
+    record2->set_wakeup_count(0);
+    record2->set_expire_count(0);
+    record2->set_active_since(0l);
+    record2->set_total_time(123l);
+    record2->set_max_time(3l);
+    record2->set_last_change(2067286206LL);
+    record2->set_prevent_suspend_time(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
new file mode 100644
index 0000000..de64e70
--- /dev/null
+++ b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PageTypeInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PageTypeInfoParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PageTypeInfoParserTest, Success) {
+    const string testFile = kTestDataPath + "pagetypeinfo.txt";
+    PageTypeInfoParser parser;
+    PageTypeInfo expected;
+
+    expected.set_page_block_order(10);
+    expected.set_pages_per_block(1024);
+
+    MigrateTypeProto* mt1 = expected.add_migrate_types();
+    mt1->set_node(0);
+    mt1->set_zone("DMA");
+    mt1->set_type("Unmovable");
+    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
+    for (auto i=0; i<11; i++) {
+        mt1->add_free_pages_count(arr1[i]);
+    }
+
+    MigrateTypeProto* mt2 = expected.add_migrate_types();
+    mt2->set_node(0);
+    mt2->set_zone("Normal");
+    mt2->set_type("Reclaimable");
+    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
+    for (auto i=0; i<11; i++) {
+        mt2->add_free_pages_count(arr2[i]);
+    }
+
+    BlockProto* block1 = expected.add_blocks();
+    block1->set_node(0);
+    block1->set_zone("DMA");
+    block1->set_unmovable(74);
+    block1->set_reclaimable(9);
+    block1->set_movable(337);
+    block1->set_cma(41);
+    block1->set_reserve(1);
+    block1->set_isolate(0);
+
+
+    BlockProto* block2 = expected.add_blocks();
+    block2->set_node(0);
+    block2->set_zone("Normal");
+    block2->set_unmovable(70);
+    block2->set_reclaimable(12);
+    block2->set_movable(423);
+    block2->set_cma(0);
+    block2->set_reserve(1);
+    block2->set_isolate(0);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/ProcrankParser_test.cpp b/cmds/incident_helper/tests/ProcrankParser_test.cpp
new file mode 100644
index 0000000..e86647a
--- /dev/null
+++ b/cmds/incident_helper/tests/ProcrankParser_test.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProcrankParser.h"
+
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class ProcrankParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(ProcrankParserTest, HasSwapInfo) {
+    const string testFile = kTestDataPath + "procrank.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_swap(1584);
+    process1->set_pswap(46);
+    process1->set_uswap(0);
+    process1->set_zswap(10);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_swap(472);
+    process2->set_pswap(342);
+    process2->set_uswap(212);
+    process2->set_zswap(75);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_swap(88164);
+    total->set_pswap(31069);
+    total->set_uswap(27612);
+    total->set_zswap(6826);
+    total->set_cmdline("TOTAL");
+
+    expected.mutable_summary()->mutable_zram()
+        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(ProcrankParserTest, NoSwapInfo) {
+    const string testFile = kTestDataPath + "procrank_short.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_cmdline("TOTAL");
+
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/statsd/.clang-format b/cmds/statsd/.clang-format
index 3d64bee..cead3a0 100644
--- a/cmds/statsd/.clang-format
+++ b/cmds/statsd/.clang-format
@@ -12,3 +12,6 @@
 PointerAlignment: Left
 TabWidth: 4
 AccessModifierOffset: -4
+IncludeCategories:
+  - Regex:    '^"Log\.h"'
+    Priority:    -1
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index db634d4..58ba42f 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -42,6 +42,8 @@
     src/metrics/EventMetricProducer.cpp \
     src/metrics/CountMetricProducer.cpp \
     src/metrics/DurationMetricProducer.cpp \
+    src/metrics/duration_helper/OringDurationTracker.cpp \
+    src/metrics/duration_helper/MaxDurationTracker.cpp \
     src/metrics/MetricsManager.cpp \
     src/metrics/metrics_manager_util.cpp \
     src/packages/UidMap.cpp \
@@ -103,7 +105,8 @@
 LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
 LOCAL_C_INCLUDES += $(statsd_common_c_includes)
 
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+    libgtest_prod
 
 LOCAL_MODULE_CLASS := EXECUTABLES
 
@@ -142,7 +145,9 @@
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
-    tests/UidMap_test.cpp
+    tests/UidMap_test.cpp \
+    tests/OringDurationTracker_test.cpp \
+    tests/MaxDurationTracker_test.cpp
 
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index cdaca1b..c0cedb1 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -60,6 +60,7 @@
 
     unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
     if (newMetricsManager->isConfigValid()) {
+        mUidMap->OnConfigUpdated(key);
         mMetricsManagers[key] = std::move(newMetricsManager);
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
         ALOGD("StatsdConfig valid");
@@ -69,14 +70,27 @@
     }
 }
 
-vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ConfigMetricsReport StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+    ConfigMetricsReport report;
+
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
-        return vector<StatsLogReport>();
+        return report;
     }
 
-    return it->second->onDumpReport();
+    auto set_key = report.mutable_config_key();
+    set_key->set_uid(key.GetUid());
+    set_key->set_name(key.GetName());
+    for (auto m : it->second->onDumpReport()) {
+        // Transfer the vector of StatsLogReport into a field
+        // TODO: perhaps we just have bytes being returned from onDumpReport and transfer bytes
+        auto dest = report.add_metrics();
+        *dest = m;
+    }
+    auto temp = mUidMap->getOutput(key);
+    report.set_allocated_uid_map(&temp);
+    return report;
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
@@ -84,6 +98,7 @@
     if (it != mMetricsManagers.end()) {
         it->second->finish();
         mMetricsManagers.erase(it);
+        mUidMap->OnConfigRemoved(key);
     }
     auto flushTime = mLastFlushTimes.find(key);
     if (flushTime != mLastFlushTimes.end()) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 6463441..0083827 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -42,7 +42,7 @@
     void OnConfigRemoved(const ConfigKey& key);
 
     // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
-    std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+    ConfigMetricsReport onDumpReport(const ConfigKey& key);
 
     /* Request a flush through a binder call. */
     void flush();
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 604753e..a856a27 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -231,6 +231,14 @@
     fprintf(out, "                parameter on eng builds.  If UID is omitted the calling\n");
     fprintf(out, "                uid is used.\n");
     fprintf(out, "  NAME          The per-uid name to use\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n");
+    fprintf(out, "  Dump all metric data for a configuration.\n");
+    fprintf(out, "  UID           The uid of the configuration. It is only possible to pass\n");
+    fprintf(out, "                the UID parameter on eng builds. If UID is omitted the\n");
+    fprintf(out, "                calling uid is used.\n");
+    fprintf(out, "  NAME          The name of the configuration\n");
 }
 
 status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
@@ -312,7 +320,7 @@
             // Automatically pick the UID
             uid = IPCThreadState::self()->getCallingUid();
             // TODO: What if this isn't a binder call? Should we fail?
-            name.assign(args[2].c_str(), args[2].size());
+            name.assign(args[1].c_str(), args[1].size());
             good = true;
         } else if (argCount == 3) {
             // If it's a userdebug or eng build, then the shell user can
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index f56c15a..953bcb3 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -16,7 +16,6 @@
 
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
-
 #include "CombinationConditionTracker.h"
 
 #include <log/logprint.h>
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index fc88a88..dbdb3b7 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -46,8 +46,6 @@
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) override;
 
-    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
-
 private:
     LogicalOperation mLogicalOperation;
     // Store index of the children Conditions.
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 055b478..bb5ddeb 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "Log.h"
-
 #include "condition/condition_util.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/LogMatchingTracker.h"
@@ -103,8 +101,6 @@
         mSliced = mSliced | sliced;
     }
 
-    virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
-
 protected:
     // We don't really need the string name, but having a name here makes log messages
     // easy to debug.
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 4889b64..1a01afa 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -27,19 +27,23 @@
 // Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
 class ConditionWizard : public virtual android::RefBase {
 public:
+    ConditionWizard(){};  // for testing
     ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
         : mAllConditions(conditionTrackers){};
 
+    virtual ~ConditionWizard(){};
+
     // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
     // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
     //                       condition.
     // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
     // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
-    ConditionState query(const int conditionIndex,
-                         const std::map<std::string, HashableDimensionKey>& conditionParameters);
+    virtual ConditionState query(
+            const int conditionIndex,
+            const std::map<std::string, HashableDimensionKey>& conditionParameters);
 
 private:
-    std::vector<sp<ConditionTracker>>& mAllConditions;
+    std::vector<sp<ConditionTracker>> mAllConditions;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index aff4768..b691faea 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -74,6 +74,13 @@
         mStopAllLogMatcherIndex = -1;
     }
 
+    mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
+                      simpleCondition.dimension().end());
+
+    if (mDimension.size() > 0) {
+        mSliced = true;
+    }
+
     mInitialized = true;
 }
 
@@ -98,12 +105,6 @@
     }
 }
 
-void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
-    VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
-    mDimensionsList.push_back(keyMatchers);
-    mSliced = true;
-}
-
 bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                                const vector<MatchingState>& eventMatcherValues,
                                                const vector<sp<ConditionTracker>>& mAllConditions,
@@ -157,18 +158,15 @@
         // TODO: handle stop all; all dimension should be cleared.
     }
 
-    if (mDimensionsList.size() > 0) {
-        for (size_t i = 0; i < mDimensionsList.size(); i++) {
-            const auto& dim = mDimensionsList[i];
-            vector<KeyValuePair> key = getDimensionKey(event, dim);
-            HashableDimensionKey hashableKey = getHashableKey(key);
-            if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
-                mSlicedConditionState[hashableKey] != newCondition) {
-                slicedChanged = true;
-                mSlicedConditionState[hashableKey] = newCondition;
-            }
-            VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+
+    if (mDimension.size() > 0) {
+        HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
+        if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+            mSlicedConditionState[hashableKey] != newCondition) {
+            slicedChanged = true;
+            mSlicedConditionState[hashableKey] = newCondition;
         }
+        VLOG("key: %s %d", hashableKey.c_str(), newCondition);
         // dump all dimensions for debugging
         if (DEBUG) {
             print(mSlicedConditionState, mName);
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 1f357f0..b72157b 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -27,8 +27,6 @@
 
 class SimpleConditionTracker : public virtual ConditionTracker {
 public:
-    // dimensions is a vector of vector because for one single condition, different metrics may be
-    // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
     SimpleConditionTracker(const std::string& name, const int index,
                            const SimpleCondition& simpleCondition,
                            const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -51,8 +49,6 @@
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) override;
 
-    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
-
 private:
     // The index of the LogEventMatcher which defines the start.
     int mStartLogMatcherIndex;
@@ -66,8 +62,11 @@
     // The index of the LogEventMatcher which defines the stop all.
     int mStopAllLogMatcherIndex;
 
-    // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
-    std::vector<std::vector<KeyMatcher>> mDimensionsList;
+    // The dimension defines at the atom level, how start and stop should match.
+    // e.g., APP_IN_FOREGROUND, the dimension should be the uid field. Each "start" and
+    // "stop" tells you the state change of a particular app. Without this dimension, this
+    // condition does not make sense.
+    std::vector<KeyMatcher> mDimension;
 
     // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
     // that StatsLogReport wants.
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index c16971a..e3ccb06 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,9 +118,10 @@
     StatsdConfig config;
     config.set_config_id(12345L);
 
-    int WAKE_LOCK_TAG_ID = 11;
+    int WAKE_LOCK_TAG_ID = 1111;  // put a fake id here to make testing easier.
     int WAKE_LOCK_UID_KEY_ID = 1;
-    int WAKE_LOCK_STATE_KEY = 3;
+    int WAKE_LOCK_NAME_KEY = 3;
+    int WAKE_LOCK_STATE_KEY = 4;
     int WAKE_LOCK_ACQUIRE_VALUE = 1;
     int WAKE_LOCK_RELEASE_VALUE = 0;
 
@@ -180,24 +181,56 @@
     link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
     link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
 
-    // Duration of an app holding wl, while screen on and app in background
+    // Duration of an app holding any wl, while screen on and app in background, slice by uid
     DurationMetric* durationMetric = config.add_duration_metric();
     durationMetric->set_metric_id(5);
-    durationMetric->set_start("APP_GET_WL");
-    durationMetric->set_stop("APP_RELEASE_WL");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
     link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
     link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
 
+    // max Duration of an app holding any wl, while screen on and app in background, slice by uid
+    durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(6);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+    keyMatcher = durationMetric->add_dimension();
+    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of an app holding any wl, while screen on and app in background
+    durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(7);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of screen on time.
+    durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(8);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    durationMetric->set_what("SCREEN_IS_ON");
+
     // Add an EventMetric to log process state change events.
     EventMetric* eventMetric = config.add_event_metric();
-    eventMetric->set_metric_id(6);
+    eventMetric->set_metric_id(9);
     eventMetric->set_what("SCREEN_TURNED_ON");
 
     // Event matchers............
@@ -272,6 +305,8 @@
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("APP_GOES_BACKGROUND");
     simpleCondition->set_stop("APP_GOES_FOREGROUND");
+    KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
+    condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -280,6 +315,16 @@
     combination_condition->add_condition("APP_IS_BACKGROUND");
     combination_condition->add_condition("SCREEN_IS_ON");
 
+    condition = config.add_condition();
+    condition->set_name("WL_STATE_PER_APP_PER_NAME");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GET_WL");
+    simpleCondition->set_stop("APP_RELEASE_WL");
+    KeyMatcher* condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+    condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+
     return config;
 }
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 69f336f..10816f6 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -21,7 +21,6 @@
 #include "CountMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -94,7 +93,7 @@
     }
 }
 
-void CountMetricProducer::onSlicedConditionMayChange() {
+void CountMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
 }
 
@@ -128,7 +127,7 @@
     // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
 }
 
-void CountMetricProducer::onConditionChanged(const bool conditionMet) {
+void CountMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
 }
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index be77e47..5d1889a 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -41,18 +41,20 @@
 
     virtual ~CountMetricProducer();
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 protected:
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index a590bc85..dfed275 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -15,11 +15,11 @@
  */
 
 #define DEBUG true
-#include "DurationMetricProducer.h"
+
 #include "Log.h"
+#include "DurationMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -34,13 +34,15 @@
 DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
-                                               const sp<ConditionWizard>& wizard)
+                                               const sp<ConditionWizard>& wizard,
+                                               const vector<KeyMatcher>& internalDimension)
     // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
     : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
       mMetric(metric),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
-      mStopAllIndex(stopAllIndex) {
+      mStopAllIndex(stopAllIndex),
+      mInternalDimension(internalDimension) {
     // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
     // them in the base class, because the proto generated CountMetric, and DurationMetric are
     // not related. Maybe we should add a template in the future??
@@ -67,34 +69,43 @@
     VLOG("~DurationMetric() called");
 }
 
+unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
+        vector<DurationBucketInfo>& bucket) {
+    switch (mMetric.type()) {
+        case DurationMetric_AggregationType_DURATION_SUM:
+            return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex,
+                                                     mCurrentBucketStartTimeNs, mBucketSizeNs,
+                                                     bucket);
+        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+            return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex,
+                                                   mCurrentBucketStartTimeNs, mBucketSizeNs,
+                                                   bucket);
+    }
+}
+
 void DurationMetricProducer::finish() {
     // TODO: write the StatsLogReport to dropbox using
     // DropboxWriter.
 }
 
-void DurationMetricProducer::onSlicedConditionMayChange() {
+void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
     // Now for each of the on-going event, check if the condition has changed for them.
+    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
-        VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
-             pair.second.state);
-        if (pair.second.state == kStopped) {
-            continue;
-        }
-        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
-                            ConditionState::kTrue;
-        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
-        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+        pair.second->onSlicedConditionMayChange(eventTime);
     }
 }
 
-void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
     // TODO: need to populate the condition change time from the event which triggers the condition
     // change, instead of using current time.
+
+    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
-        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+        pair.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
@@ -107,7 +118,7 @@
     }
     for (const auto& bucket : buckets) {
         data->add_bucket_info()->CopyFrom(bucket);
-        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+        VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
              bucket.end_bucket_nanos(), bucket.duration_nanos());
     }
 }
@@ -120,7 +131,7 @@
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    flushIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
     report.set_end_report_nanos(mCurrentBucketStartTimeNs);
 
     StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
@@ -137,216 +148,50 @@
     return report;
 };
 
-void DurationMetricProducer::onMatchedLogEventInternal(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
-        const LogEvent& event) {
-    flushDurationIfNeeded(event.GetTimestampNs());
-
-    if (matcherIndex == mStopAllIndex) {
-        noteStopAll(event.GetTimestampNs());
-        return;
-    }
-
-    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) {
-        // add the durationInfo for the current bucket.
-        auto& durationInfo = mCurrentSlicedDuration[eventKey];
-        durationInfo.conditionKeys = conditionKeys;
-    }
-
-    if (matcherIndex == mStartIndex) {
-        VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(),
-             condition);
-        noteStart(eventKey, condition, event.GetTimestampNs());
-    } else if (matcherIndex == mStopIndex) {
-        VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(),
-             condition);
-        noteStop(eventKey, event.GetTimestampNs());
-    }
-}
-
-void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
-                                                  const bool conditionMet,
-                                                  const uint64_t eventTime) {
-    flushDurationIfNeeded(eventTime);
-
-    auto it = mCurrentSlicedDuration.find(key);
-    if (it == mCurrentSlicedDuration.end()) {
-        return;
-    }
-
-    switch (it->second.state) {
-        case kStarted:
-            // if condition becomes false, kStarted -> kPaused. Record the current duration.
-            if (!conditionMet) {
-                it->second.state = DurationState::kPaused;
-                it->second.lastDuration =
-                        updateDuration(it->second.lastDuration,
-                                       eventTime - it->second.lastStartTime, mMetric.type());
-                VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
-                     key.c_str());
-            }
-            break;
-        case kStopped:
-            // nothing to do if it's stopped.
-            break;
-        case kPaused:
-            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
-            // change time.
-            if (conditionMet) {
-                it->second.state = DurationState::kStarted;
-                it->second.lastStartTime = eventTime;
-                VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
-            }
-            break;
-    }
-}
-
-void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
-                                       const uint64_t eventTime) {
-    // this will add an empty bucket for this key if it didn't exist before.
-    DurationInfo& duration = mCurrentSlicedDuration[key];
-
-    switch (duration.state) {
-        case kStarted:
-            // It's safe to do nothing here. even if condition is not true, it means we are about
-            // to receive the condition change event.
-            break;
-        case kPaused:
-            // Safe to do nothing here. kPaused is waiting for the condition change.
-            break;
-        case kStopped:
-            if (!conditionMet) {
-                // event started, but we need to wait for the condition to become true.
-                duration.state = DurationState::kPaused;
-                break;
-            }
-            duration.state = DurationState::kStarted;
-            duration.lastStartTime = eventTime;
-            break;
-    }
-}
-
-void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
-    if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
-        // we didn't see a start event before. do nothing.
-        return;
-    }
-    DurationInfo& duration = mCurrentSlicedDuration[key];
-
-    switch (duration.state) {
-        case DurationState::kStopped:
-            // already stopped, do nothing.
-            break;
-        case DurationState::kStarted: {
-            duration.state = DurationState::kStopped;
-            int64_t durationTime = eventTime - duration.lastStartTime;
-            VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
-                 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
-            duration.lastDuration =
-                    updateDuration(duration.lastDuration, durationTime, mMetric.type());
-            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
-            break;
-        }
-        case DurationState::kPaused: {
-            duration.state = DurationState::kStopped;
-            break;
-        }
-    }
-}
-
-int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
-                                               const int64_t durationTime,
-                                               const DurationMetric_AggregationType type) {
-    int64_t result = lastDuration;
-    switch (type) {
-        case DurationMetric_AggregationType_DURATION_SUM:
-            result += durationTime;
-            break;
-        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
-            if (lastDuration < durationTime) {
-                result = durationTime;
-            }
-            break;
-        case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
-            if (lastDuration > durationTime) {
-                result = durationTime;
-            }
-            break;
-    }
-    return result;
-}
-
-void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
-    for (auto& duration : mCurrentSlicedDuration) {
-        noteStop(duration.first, eventTime);
-    }
-}
-
-// When a new matched event comes in, we check if event falls into the current
-// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
-void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
         return;
     }
 
-    // adjust the bucket start time
-    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
-    DurationBucketInfo info;
-    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(endTime);
-
-    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
-    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-    VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
-         (long long)mCurrentBucketStartTimeNs);
-
+    VLOG("flushing...........");
     for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
-        int64_t finalDuration = it->second.lastDuration;
-        if (it->second.state == kStarted) {
-            // the event is still on-going, duration needs to be updated.
-            int64_t durationTime = endTime - it->second.lastStartTime;
-            finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
-        }
-
-        VLOG("  final duration for last bucket: %lld", (long long)finalDuration);
-
-        // Don't record empty bucket.
-        if (finalDuration != 0) {
-            info.set_duration_nanos(finalDuration);
-            // it will auto create new vector of CountbucketInfo if the key is not found.
-            auto& bucketList = mPastBuckets[it->first];
-            bucketList.push_back(info);
-        }
-
-        // if the event is still on-going, add the buckets between previous bucket and now. Because
-        // the event has been going on across all the buckets in between.
-        // |prev_bucket|...|..|...|now_bucket|
-        if (it->second.state == kStarted) {
-            for (int i = 1; i < numBucketsForward; i++) {
-                DurationBucketInfo info;
-                info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
-                info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
-                info.set_duration_nanos(mBucketSizeNs);
-                auto& bucketList = mPastBuckets[it->first];
-                bucketList.push_back(info);
-                VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
-            }
-        }
-
-        if (it->second.state == DurationState::kStopped) {
-            // No need to keep buckets for events that were stopped before. If the event starts
-            // again, we will add it back.
+        if (it->second->flushIfNeeded(eventTime)) {
+            VLOG("erase bucket for key %s", it->first.c_str());
             mCurrentSlicedDuration.erase(it);
-        } else {
-            // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
-            // and duration.
-            it->second.lastStartTime = mCurrentBucketStartTimeNs;
-            it->second.lastDuration = 0;
         }
     }
+
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+}
+
+void DurationMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+        const LogEvent& event) {
+    flushIfNeeded(event.GetTimestampNs());
+
+    if (matcherIndex == mStopAllIndex) {
+        for (auto& pair : mCurrentSlicedDuration) {
+            pair.second->noteStopAll(event.GetTimestampNs());
+        }
+        return;
+    }
+
+    HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+
+    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+        mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+    }
+
+    auto it = mCurrentSlicedDuration.find(eventKey);
+
+    if (matcherIndex == mStartIndex) {
+        it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
+
+    } else if (matcherIndex == mStopIndex) {
+        it->second->noteStop(atomKey, event.GetTimestampNs());
+    }
 }
 
 size_t DurationMetricProducer::byteSize() {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 8820403..5b302b4 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -22,6 +22,9 @@
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
+#include "duration_helper/DurationTracker.h"
+#include "duration_helper/MaxDurationTracker.h"
+#include "duration_helper/OringDurationTracker.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
@@ -32,47 +35,29 @@
 namespace os {
 namespace statsd {
 
-enum DurationState {
-    kStopped = 0,  // The event is stopped.
-    kStarted = 1,  // The event is on going.
-    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
-                   // turns to true, kPaused will become kStarted.
-};
-
-// Hold duration information for current on-going bucket.
-struct DurationInfo {
-    DurationState state;
-    // most recent start time.
-    int64_t lastStartTime;
-    // existing duration in current bucket. Eventually, the duration will be aggregated in
-    // the way specified in AggregateType (Sum, Max, or Min).
-    int64_t lastDuration;
-    // cache the HashableDimensionKeys we need to query the condition for this duration event.
-    std::map<string, HashableDimensionKey> conditionKeys;
-
-    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
-};
-
 class DurationMetricProducer : public MetricProducer {
 public:
     DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
                            const size_t startIndex, const size_t stopIndex,
-                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
+                           const vector<KeyMatcher>& internalDimension);
 
     virtual ~DurationMetricProducer();
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 protected:
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
@@ -91,26 +76,21 @@
     // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
     const size_t mStopAllIndex;
 
+    // The dimension from the atom predicate. e.g., uid, wakelock name.
+    const vector<KeyMatcher> mInternalDimension;
+
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
+            mCurrentSlicedDuration;
 
     void flushDurationIfNeeded(const uint64_t newEventTime);
 
-    void noteStart(const HashableDimensionKey& key, const bool conditionMet,
-                   const uint64_t eventTime);
+    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucketInfo>& bucket);
 
-    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
-
-    void noteStopAll(const uint64_t eventTime);
-
-    static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
-                                  const DurationMetric_AggregationType type);
-
-    void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
-                              const uint64_t eventTime);
+    void flushIfNeeded(uint64_t timestamp);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 7e06105..dd23d66 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -20,7 +20,6 @@
 #include "EventMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -78,7 +77,7 @@
 void EventMetricProducer::finish() {
 }
 
-void EventMetricProducer::onSlicedConditionMayChange() {
+void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
 }
 
 StatsLogReport EventMetricProducer::onDumpReport() {
@@ -105,7 +104,7 @@
     return StatsLogReport();
 }
 
-void EventMetricProducer::onConditionChanged(const bool conditionMet) {
+void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
 }
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 14fa31c..72df0a7 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -43,18 +43,20 @@
                                    const std::map<std::string, HashableDimensionKey>& conditionKey,
                                    bool condition, const LogEvent& event) override;
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 private:
     const EventMetric mMetric;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 80eb527..2083695 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -50,9 +50,9 @@
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event);
 
-    virtual void onConditionChanged(const bool condition) = 0;
+    virtual void onConditionChanged(const bool condition, const uint64_t eventTime) = 0;
 
-    virtual void onSlicedConditionMayChange() = 0;
+    virtual void onSlicedConditionMayChange(const uint64_t eventTime) = 0;
 
     // This is called when the metric collecting is done, e.g., when there is a new configuration
     // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 4fa3965..d1df8aa 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -74,6 +74,7 @@
     }
 
     int tagId = event.GetTagId();
+    uint64_t eventTime = event.GetTimestampNs();
     if (mTagIds.find(tagId) == mTagIds.end()) {
         // not interesting...
         return;
@@ -124,13 +125,14 @@
                 // metric cares about non sliced condition, and it's changed.
                 // Push the new condition to it directly.
                 if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
-                    mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+                    mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
+                                                                         eventTime);
                     // metric cares about sliced conditions, and it may have changed. Send
                     // notification, and the metric can query the sliced conditions that are
                     // interesting to it.
                 } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
                            slicedChangedCache[i]) {
-                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
+                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
                 }
             }
         }
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
new file mode 100644
index 0000000..0d0d9a4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DURATION_TRACKER_H
+#define DURATION_TRACKER_H
+
+#include "condition/ConditionWizard.h"
+#include "stats_util.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+    kStopped = 0,  // The event is stopped.
+    kStarted = 1,  // The event is on going.
+    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
+                   // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for one atom level duration in current on-going bucket.
+struct DurationInfo {
+    DurationState state;
+    // most recent start time.
+    int64_t lastStartTime;
+    // existing duration in current bucket.
+    int64_t lastDuration;
+    // TODO: Optimize the way we track sliced condition in duration metrics.
+    // cache the HashableDimensionKeys we need to query the condition for this duration event.
+    ConditionKey conditionKeys;
+
+    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationTracker {
+public:
+    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
+                    uint64_t bucketSizeNs, std::vector<DurationBucketInfo>& bucket)
+        : mWizard(wizard),
+          mConditionTrackerIndex(conditionIndex),
+          mCurrentBucketStartTimeNs(currentBucketStartNs),
+          mBucketSizeNs(bucketSizeNs),
+          mBucket(bucket),
+          mDuration(0){};
+    virtual ~DurationTracker(){};
+    virtual void noteStart(const HashableDimensionKey& key, bool condition,
+                           const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) = 0;
+    virtual void noteStopAll(const uint64_t eventTime) = 0;
+    virtual void onSlicedConditionMayChange(const uint64_t timestamp) = 0;
+    virtual void onConditionChanged(bool condition, const uint64_t timestamp) = 0;
+    // Flush stale buckets if needed, and return true if the tracker has no on-going duration
+    // events, so that the owner can safely remove the tracker.
+    virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
+
+protected:
+    sp<ConditionWizard> mWizard;
+
+    int mConditionTrackerIndex;
+
+    uint64_t mCurrentBucketStartTimeNs;
+
+    int64_t mBucketSizeNs;
+
+    std::vector<DurationBucketInfo>& mBucket;  // where to write output
+
+    int64_t mDuration;  // current recorded duration result
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
new file mode 100644
index 0000000..856ca9d
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true
+
+#include "Log.h"
+#include "MaxDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                       std::vector<DurationBucketInfo>& bucket)
+    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
+}
+
+void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+                                   const uint64_t eventTime, const ConditionKey& conditionKey) {
+    // this will construct a new DurationInfo if this key didn't exist.
+    DurationInfo& duration = mInfos[key];
+    duration.conditionKeys = conditionKey;
+    VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
+
+    switch (duration.state) {
+        case kStarted:
+            // The same event is already started. Because we are not counting nesting, so ignore.
+            break;
+        case kPaused:
+            // Safe to do nothing here. Paused means started but condition is false.
+            break;
+        case kStopped:
+            if (!condition) {
+                // event started, but we need to wait for the condition to become true.
+                duration.state = DurationState::kPaused;
+            } else {
+                duration.state = DurationState::kStarted;
+                duration.lastStartTime = eventTime;
+            }
+            break;
+    }
+}
+
+void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+    VLOG("MaxDuration: key %s stop", key.c_str());
+    if (mInfos.find(key) == mInfos.end()) {
+        // we didn't see a start event before. do nothing.
+        return;
+    }
+    DurationInfo& duration = mInfos[key];
+
+    switch (duration.state) {
+        case DurationState::kStopped:
+            // already stopped, do nothing.
+            break;
+        case DurationState::kStarted: {
+            duration.state = DurationState::kStopped;
+            int64_t durationTime = eventTime - duration.lastStartTime;
+            VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(), (long long)duration.lastStartTime,
+                 (long long)eventTime, (long long)durationTime);
+            duration.lastDuration = duration.lastDuration + durationTime;
+            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            break;
+        }
+        case DurationState::kPaused: {
+            duration.state = DurationState::kStopped;
+            break;
+        }
+    }
+
+    if (duration.lastDuration > mDuration) {
+        mDuration = duration.lastDuration;
+        VLOG("Max: new max duration: %lld", (long long)mDuration);
+    }
+    // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
+    // same name, they are still considered as different atom durations.
+    mInfos.erase(key);
+}
+void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
+    for (auto& pair : mInfos) {
+        noteStop(pair.first, eventTime);
+    }
+}
+
+bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return false;
+    }
+
+    VLOG("MaxDurationTracker flushing.....");
+
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+    bool hasOnGoingStartedEvent = false;  // a kStarted event last across bucket boundaries.
+    bool hasPendingEvent =
+            false;  // has either a kStarted or kPaused event across bucket boundaries
+                    // meaning we need to carry them over to the new bucket.
+    for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
+        int64_t finalDuration = it->second.lastDuration;
+        if (it->second.state == kStarted) {
+            // the event is still on-going, duration needs to be updated.
+            // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
+            // duration between lastStartTime and bucketEnd.
+            int64_t durationTime = endTime - it->second.lastStartTime;
+
+            finalDuration += durationTime;
+            VLOG("  unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
+            // if the event is still on-going, we need to fill the buckets between prev_bucket and
+            // now_bucket. |prev_bucket|...|..|...|now_bucket|
+            hasOnGoingStartedEvent = true;
+        }
+
+        if (finalDuration > mDuration) {
+            mDuration = finalDuration;
+        }
+
+        if (it->second.state == DurationState::kStopped) {
+            // No need to keep buckets for events that were stopped before.
+            mInfos.erase(it);
+        } else {
+            hasPendingEvent = true;
+            // for kPaused, and kStarted event, we will keep track of them, and reset the start time
+            // and duration.
+            it->second.lastStartTime = mCurrentBucketStartTimeNs;
+            it->second.lastDuration = 0;
+        }
+    }
+
+    if (mDuration != 0) {
+        info.set_duration_nanos(mDuration);
+        mBucket.push_back(info);
+        VLOG("  final duration for last bucket: %lld", (long long)mDuration);
+    }
+
+    mDuration = 0;
+    if (hasOnGoingStartedEvent) {
+        for (int i = 1; i < numBucketsForward; i++) {
+            DurationBucketInfo info;
+            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+            info.set_duration_nanos(mBucketSizeNs);
+            mBucket.push_back(info);
+            VLOG("  filling gap bucket with duration %lld", (long long)mBucketSizeNs);
+        }
+    }
+    // If this tracker has no pending events, tell owner to remove.
+    return !hasPendingEvent;
+}
+
+void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+    //  VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+    // Now for each of the on-going event, check if the condition has changed for them.
+    for (auto& pair : mInfos) {
+        if (pair.second.state == kStopped) {
+            continue;
+        }
+        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+                            ConditionState::kTrue;
+        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+        noteConditionChanged(pair.first, conditionMet, timestamp);
+    }
+}
+
+void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    for (auto& pair : mInfos) {
+        noteConditionChanged(pair.first, condition, timestamp);
+    }
+}
+
+void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+                                              const uint64_t timestamp) {
+    auto it = mInfos.find(key);
+    if (it == mInfos.end()) {
+        return;
+    }
+
+    switch (it->second.state) {
+        case kStarted:
+            // if condition becomes false, kStarted -> kPaused. Record the current duration.
+            if (!conditionMet) {
+                it->second.state = DurationState::kPaused;
+                it->second.lastDuration += (timestamp - it->second.lastStartTime);
+
+                VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
+            }
+            break;
+        case kStopped:
+            // nothing to do if it's stopped.
+            break;
+        case kPaused:
+            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+            // change time.
+            if (conditionMet) {
+                it->second.state = DurationState::kStarted;
+                it->second.lastStartTime = timestamp;
+                VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
+            }
+            break;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
new file mode 100644
index 0000000..c74d070
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MAX_DURATION_TRACKER_H
+#define MAX_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks a pool of atom durations, and output the max duration for each bucket.
+// To get max duration, we need to keep track of each individual durations, and compare them when
+// they stop or bucket expires.
+class MaxDurationTracker : public DurationTracker {
+public:
+    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                       std::vector<DurationBucketInfo>& bucket);
+    void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+                   const ConditionKey& conditionKey) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStopAll(const uint64_t eventTime) override;
+    bool flushIfNeeded(uint64_t timestampNs) override;
+    void onSlicedConditionMayChange(const uint64_t timestamp) override;
+    void onConditionChanged(bool condition, const uint64_t timestamp) override;
+
+private:
+    std::map<HashableDimensionKey, DurationInfo> mInfos;
+
+    void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+                              const uint64_t timestamp);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // MAX_DURATION_TRACKER_H
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
new file mode 100644
index 0000000..e045fb4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG true
+#include "Log.h"
+#include "OringDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                                           uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                           std::vector<DurationBucketInfo>& bucket)
+    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
+      mStarted(),
+      mPaused() {
+    mLastStartTime = 0;
+}
+
+void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+                                     const uint64_t eventTime, const ConditionKey& conditionKey) {
+    if (condition) {
+        if (mStarted.size() == 0) {
+            mLastStartTime = eventTime;
+            VLOG("record first start....");
+        }
+        mStarted.insert(key);
+    } else {
+        mPaused.insert(key);
+    }
+
+    if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+        mConditionKeyMap[key] = conditionKey;
+    }
+
+    VLOG("Oring: %s start, condition %d", key.c_str(), condition);
+}
+
+void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp) {
+    VLOG("Oring: %s stop", key.c_str());
+    auto it = mStarted.find(key);
+    if (it != mStarted.end()) {
+        mStarted.erase(it);
+        if (mStarted.empty()) {
+            mDuration += (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
+                 (long long)mDuration);
+        }
+    }
+
+    mPaused.erase(key);
+    mConditionKeyMap.erase(key);
+}
+void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+    if (!mStarted.empty()) {
+        mDuration += (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
+             (long long)mDuration);
+    }
+
+    mStarted.clear();
+    mPaused.clear();
+    mConditionKeyMap.clear();
+}
+
+bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return false;
+    }
+    VLOG("OringDurationTracker Flushing.............");
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+    if (mStarted.size() > 0) {
+        mDuration += (endTime - mLastStartTime);
+    }
+    if (mDuration != 0) {
+        info.set_duration_nanos(mDuration);
+        // it will auto create new vector of CountbucketInfo if the key is not found.
+        mBucket.push_back(info);
+        VLOG("  duration: %lld", (long long)mDuration);
+    }
+
+    if (mStarted.size() > 0) {
+        for (int i = 1; i < numBucketsForward; i++) {
+            DurationBucketInfo info;
+            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+            info.set_duration_nanos(mBucketSizeNs);
+            mBucket.push_back(info);
+            VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+        }
+    }
+    mLastStartTime = mCurrentBucketStartTimeNs;
+    mDuration = 0;
+
+    // if all stopped, then tell owner it's safe to remove this tracker.
+    return mStarted.empty() && mPaused.empty();
+}
+
+void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+    vector<HashableDimensionKey> startedToPaused;
+    vector<HashableDimensionKey> pausedToStarted;
+    if (!mStarted.empty()) {
+        for (auto it = mStarted.begin(); it != mStarted.end();) {
+            auto key = *it;
+            if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+                VLOG("Key %s dont have condition key", key.c_str());
+                ++it;
+                continue;
+            }
+            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
+                ConditionState::kTrue) {
+                it = mStarted.erase(it);
+                startedToPaused.push_back(key);
+                VLOG("Key %s started -> paused", key.c_str());
+            } else {
+                ++it;
+            }
+        }
+
+        if (mStarted.empty()) {
+            mDuration += (timestamp - mLastStartTime);
+            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
+                 (long long)mDuration);
+        }
+    }
+
+    if (!mPaused.empty()) {
+        for (auto it = mPaused.begin(); it != mPaused.end();) {
+            auto key = *it;
+            if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+                VLOG("Key %s dont have condition key", key.c_str());
+                ++it;
+                continue;
+            }
+            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
+                ConditionState::kTrue) {
+                it = mPaused.erase(it);
+                pausedToStarted.push_back(key);
+                VLOG("Key %s paused -> started", key.c_str());
+            } else {
+                ++it;
+            }
+        }
+
+        if (mStarted.empty() && pausedToStarted.size() > 0) {
+            mLastStartTime = timestamp;
+        }
+    }
+
+    mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
+    mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+}
+
+void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    if (condition) {
+        if (!mPaused.empty()) {
+            VLOG("Condition true, all started");
+            if (mStarted.empty()) {
+                mLastStartTime = timestamp;
+            }
+            mStarted.insert(mPaused.begin(), mPaused.end());
+        }
+    } else {
+        if (!mStarted.empty()) {
+            VLOG("Condition false, all paused");
+            mDuration += (timestamp - mLastStartTime);
+            mPaused.insert(mStarted.begin(), mStarted.end());
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
new file mode 100644
index 0000000..5425251
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ORING_DURATION_TRACKER_H
+#define ORING_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include <set>
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
+class OringDurationTracker : public DurationTracker {
+public:
+    OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                         uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                         std::vector<DurationBucketInfo>& bucket);
+    void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+                   const ConditionKey& conditionKey) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStopAll(const uint64_t eventTime) override;
+    void onSlicedConditionMayChange(const uint64_t timestamp) override;
+    void onConditionChanged(bool condition, const uint64_t timestamp) override;
+    bool flushIfNeeded(uint64_t timestampNs) override;
+
+private:
+    // We don't need to keep track of individual durations. The information that's needed is:
+    // 1) which keys are started. We record the first start time.
+    // 2) which keys are paused (started but condition was false)
+    // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty,
+    //    it means everything has stopped, we then record the end time.
+    std::set<HashableDimensionKey> mStarted;
+    std::set<HashableDimensionKey> mPaused;
+    int64_t mLastStartTime;
+    std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // ORING_DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index e90f998..3b3ffb7 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -33,6 +33,8 @@
 namespace statsd {
 
 bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+                                 const bool usedForDimension,
+                                 const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                                  const unordered_map<string, int>& logTrackerMap,
                                  unordered_map<int, std::vector<int>>& trackerToMetricMap,
                                  int& logTrackerIndex) {
@@ -41,6 +43,12 @@
         ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
         return false;
     }
+    if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
+        ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
+              "\"what\" can only about one atom type.",
+              what.c_str());
+        return false;
+    }
     logTrackerIndex = logTrackerIt->second;
     auto& metric_list = trackerToMetricMap[logTrackerIndex];
     metric_list.push_back(metricIndex);
@@ -68,8 +76,7 @@
         }
         allConditionTrackers[condition_it->second]->setSliced(true);
         allConditionTrackers[it->second]->setSliced(true);
-        allConditionTrackers[it->second]->addDimensions(
-                vector<KeyMatcher>(link.key_in_condition().begin(), link.key_in_condition().end()));
+        // TODO: We need to verify the link is valid.
     }
     conditionIndex = condition_it->second;
 
@@ -176,6 +183,7 @@
 
 bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
                  const unordered_map<string, int>& conditionTrackerMap,
+                 const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -196,8 +204,9 @@
 
         int metricIndex = allMetricProducers.size();
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+                                         allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+                                         trackerIndex)) {
             return false;
         }
 
@@ -215,26 +224,49 @@
     for (int i = 0; i < config.duration_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const DurationMetric& metric = config.duration_metric(i);
+
+        auto what_it = conditionTrackerMap.find(metric.what());
+        if (what_it == conditionTrackerMap.end()) {
+            ALOGE("DurationMetric's \"what\" is invalid");
+            return false;
+        }
+
+        const Condition& durationWhat = config.condition(what_it->second);
+
+        if (durationWhat.contents_case() != Condition::ContentsCase::kSimpleCondition) {
+            ALOGE("DurationMetric's \"what\" must be a simple condition");
+            return false;
+        }
+
+        const auto& simpleCondition = durationWhat.simple_condition();
+
         int trackerIndices[3] = {-1, -1, -1};
-        if (!metric.has_start() ||
-            !handleMetricWithLogTrackers(metric.start(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[0])) {
+        if (!simpleCondition.has_start() ||
+            !handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
             ALOGE("Duration metrics must specify a valid the start event matcher");
             return false;
         }
 
-        if (metric.has_stop() &&
-            !handleMetricWithLogTrackers(metric.stop(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[1])) {
+        if (simpleCondition.has_stop() &&
+            !handleMetricWithLogTrackers(simpleCondition.stop(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
             return false;
         }
 
-        if (metric.has_stop_all() &&
-            !handleMetricWithLogTrackers(metric.stop_all(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[2])) {
+        if (simpleCondition.has_stop_all() &&
+            !handleMetricWithLogTrackers(simpleCondition.stop_all(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
             return false;
         }
 
+        vector<KeyMatcher> internalDimension;
+        internalDimension.insert(internalDimension.begin(), simpleCondition.dimension().begin(),
+                                 simpleCondition.dimension().end());
+
         int conditionIndex = -1;
 
         if (metric.has_predicate()) {
@@ -243,9 +275,9 @@
                                        conditionToMetricMap);
         }
 
-        sp<MetricProducer> durationMetric =
-                new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
-                                           trackerIndices[1], trackerIndices[2], wizard);
+        sp<MetricProducer> durationMetric = new DurationMetricProducer(
+                metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
+                wizard, internalDimension);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -258,8 +290,8 @@
             return false;
         }
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -299,8 +331,9 @@
         return false;
     }
 
-    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
-                     allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
+    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
+                     allConditionTrackers, allMetricProducers, conditionToMetricMap,
+                     trackerToMetricMap)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 6722eb3..c91b7fc 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -75,6 +75,7 @@
         const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
         const std::unordered_map<std::string, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+        const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
         vector<sp<ConditionTracker>>& allConditionTrackers,
         std::vector<sp<MetricProducer>>& allMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index 8b948de..13e776f 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -29,6 +29,9 @@
     // Uid map will notify this listener that the app with apk name and uid has been upgraded to
     // the specified version.
     virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int version) = 0;
+
+    // Notify interested listeners that the given apk and uid combination no longer exits.
+    virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index f4621ee..d83c3a4 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -52,25 +52,35 @@
 
 void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                        const vector<String16>& packageName) {
+    updateMap(time(nullptr) * 1000000000, uid, versionCode, packageName);
+}
+
+void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+                       const vector<int32_t>& versionCode, const vector<String16>& packageName) {
     lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
 
     mMap.clear();
-    for (unsigned long j = 0; j < uid.size(); j++) {
+    for (size_t j = 0; j < uid.size(); j++) {
         mMap.insert(make_pair(uid[j],
                               AppData(string(String8(packageName[j]).string()), versionCode[j])));
     }
 
-    if (mOutput.initial_size() == 0) {  // Provide the initial states in the mOutput proto
-        for (unsigned long j = 0; j < uid.size(); j++) {
-            auto t = mOutput.add_initial();
-            t->set_app(string(String8(packageName[j]).string()));
-            t->set_version(int(versionCode[j]));
-            t->set_uid(uid[j]);
-        }
+    auto snapshot = mOutput.add_snapshots();
+    snapshot->set_timestamp_nanos(timestamp);
+    for (size_t j = 0; j < uid.size(); j++) {
+        auto t = snapshot->add_package_info();
+        t->set_name(string(String8(packageName[j]).string()));
+        t->set_version(int(versionCode[j]));
+        t->set_uid(uid[j]);
     }
 }
 
 void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
+    updateApp(time(nullptr) * 1000000000, app_16, uid, versionCode);
+}
+
+void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
+                       const int32_t& versionCode) {
     lock_guard<mutex> lock(mMutex);
 
     string app = string(String8(app_16).string());
@@ -82,7 +92,7 @@
 
     auto log = mOutput.add_changes();
     log->set_deletion(false);
-    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_timestamp_nanos(timestamp);
     log->set_app(app);
     log->set_uid(uid);
     log->set_version(versionCode);
@@ -102,13 +112,20 @@
 }
 
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
+    removeApp(time(nullptr) * 1000000000, app_16, uid);
+}
+void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
     lock_guard<mutex> lock(mMutex);
 
     string app = string(String8(app_16).string());
 
+    for (auto it : mSubscribers) {
+        it->notifyAppRemoved(app, uid);
+    }
+
     auto log = mOutput.add_changes();
     log->set_deletion(true);
-    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_timestamp_nanos(timestamp);
     log->set_app(app);
     log->set_uid(uid);
 
@@ -133,21 +150,67 @@
     mSubscribers.erase(producer);
 }
 
-UidMapping UidMap::getAndClearOutput() {
-    lock_guard<mutex> lock(mMutex);  // Lock for updates
-
-    auto ret = UidMapping(mOutput);  // Copy that will be returned.
+void UidMap::clearOutput() {
     mOutput.Clear();
 
     // Re-initialize the initial state for the outputs. This results in extra data being uploaded
-    // but helps ensure we can't re-construct the UID->app name, versionCode mapping in server.
+    // but helps ensure we can re-construct the UID->app name, versionCode mapping in server.
+    auto snapshot = mOutput.add_snapshots();
     for (auto it : mMap) {
-        auto t = mOutput.add_initial();
-        t->set_app(it.second.packageName);
+        auto t = snapshot->add_package_info();
+        t->set_name(it.second.packageName);
         t->set_version(it.second.versionCode);
         t->set_uid(it.first);
     }
+}
 
+int64_t UidMap::getMinimumTimestampNs() {
+    int64_t m = 0;
+    for (auto it : mLastUpdatePerConfigKey) {
+        if (m == 0) {
+            m = it.second;
+        } else if (it.second < m) {
+            m = it.second;
+        }
+    }
+    return m;
+}
+
+UidMapping UidMap::getOutput(const ConfigKey& key) {
+    return getOutput(time(nullptr) * 1000000000, key);
+}
+
+UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) {
+    lock_guard<mutex> lock(mMutex);  // Lock for updates
+
+    auto ret = UidMapping(mOutput);  // Copy that will be returned.
+    int64_t prevMin = getMinimumTimestampNs();
+    mLastUpdatePerConfigKey[key] = timestamp;
+    int64_t newMin = getMinimumTimestampNs();
+
+    if (newMin > prevMin) {
+        int64_t cutoff_nanos = newMin;
+        auto snapshots = mOutput.mutable_snapshots();
+        auto it_snapshots = snapshots->cbegin();
+        while (it_snapshots != snapshots->cend()) {
+            if (it_snapshots->timestamp_nanos() < cutoff_nanos) {
+                // it_snapshots now points to the following element.
+                it_snapshots = snapshots->erase(it_snapshots);
+            } else {
+                ++it_snapshots;
+            }
+        }
+        auto deltas = mOutput.mutable_changes();
+        auto it_deltas = deltas->cbegin();
+        while (it_deltas != deltas->cend()) {
+            if (it_deltas->timestamp_nanos() < cutoff_nanos) {
+                // it_deltas now points to the following element.
+                it_deltas = deltas->erase(it_deltas);
+            } else {
+                ++it_deltas;
+            }
+        }
+    }
     return ret;
 }
 
@@ -160,6 +223,14 @@
     }
 }
 
+void UidMap::OnConfigUpdated(const ConfigKey& key) {
+    mLastUpdatePerConfigKey[key] = -1;
+}
+
+void UidMap::OnConfigRemoved(const ConfigKey& key) {
+    mLastUpdatePerConfigKey.erase(key);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index d550372..bf120e0 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -17,11 +17,14 @@
 #ifndef STATSD_UIDMAP_H
 #define STATSD_UIDMAP_H
 
+#include "config/ConfigKey.h"
+#include "config/ConfigListener.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "packages/PackageInfoListener.h"
 
 #include <binder/IResultReceiver.h>
 #include <binder/IShellCallback.h>
+#include <gtest/gtest_prod.h>
 #include <log/logprint.h>
 #include <stdio.h>
 #include <utils/RefBase.h>
@@ -51,17 +54,19 @@
      * All three inputs must be the same size, and the jth element in each array refers to the same
      * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
      */
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
     void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                    const vector<String16>& packageName);
 
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+    void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
+    void removeApp(const String16& packageName, const int32_t& uid);
+
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
     bool hasApp(int uid, const string& packageName) const;
 
     int getAppVersion(int uid, const string& packageName) const;
 
-    void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
-    void removeApp(const String16& packageName, const int32_t& uid);
-
     // Helper for debugging contents of this uid map. Can be triggered with:
     // adb shell cmd stats print-uid-map
     void printUidMap(FILE* out);
@@ -73,13 +78,36 @@
     // Remove the listener from the set of metric producers that subscribe to updates.
     void removeListener(sp<PackageInfoListener> producer);
 
-    // Grabs the current output contents and then clears it.
-    UidMapping getAndClearOutput();
+    // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
+    void OnConfigUpdated(const ConfigKey& key);
+
+    // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date.
+    void OnConfigRemoved(const ConfigKey& key);
+
+    // Gets the output. If every config key has received the output, then the output is cleared.
+    UidMapping getOutput(const ConfigKey& key);
+
+    // Forces the output to be cleared. We still generate a snapshot based on the current state.
+    // This results in extra data uploaded but helps us reconstruct the uid mapping on the server
+    // in case we lose a previous upload.
+    void clearOutput();
 
 private:
+    void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+                   const vector<int32_t>& versionCode, const vector<String16>& packageName);
+
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+    void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
+                   const int32_t& versionCode);
+    void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
+
+    UidMapping getOutput(const int64_t& timestamp, const ConfigKey& key);
+
     // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
     mutable mutex mMutex;
 
+    // Maps uid to application data. This must be multimap since there is a feature in Android for
+    // multiple apps to share the same uid.
     std::unordered_multimap<int, AppData> mMap;
 
     // We prepare the output proto as apps are updated, so that we can grab the current output.
@@ -87,6 +115,17 @@
 
     // Metric producers that should be notified if there's an upgrade in any app.
     set<sp<PackageInfoListener>> mSubscribers;
+
+    // Mapping of config keys we're aware of to the epoch time they last received an update. This
+    // lets us know it's safe to delete events older than the oldest update. The value is nanosec.
+    // Value of -1 denotes this config key has never received an upload.
+    std::unordered_map<ConfigKey, int64_t> mLastUpdatePerConfigKey;
+
+    // Returns the minimum value from mConfigKeys.
+    int64_t getMinimumTimestampNs();
+
+    // Allows unit-test to access private methods.
+    FRIEND_TEST(UidMapTest, TestClearingOutput);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index 74ee332..3789baf 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -69,7 +69,7 @@
         WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
         WifiScanStateChanged wifi_scan_state_changed = 39;
         PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
-
+        SettingChanged setting_changed = 41;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 }
@@ -479,7 +479,7 @@
  * Logs battery level (percent full, from 0 to 100).
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message BatteryLevelChanged {
     // Battery level. Should be in [0, 100].
@@ -490,7 +490,7 @@
  * Logs change in charging status of the device.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message ChargingStateChanged {
     // TODO: Link directly to BatteryManager.java's constants (via a proto).
@@ -508,7 +508,7 @@
  * Logs whether the device is plugged in, and what power source it is using.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message PluggedStateChanged {
     // TODO: Link directly to BatteryManager.java's constants (via a proto).
@@ -529,7 +529,7 @@
  * Logs the temperature of the device, in tenths of a degree Celsius.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message DeviceTemperatureReported {
     // Temperature in tenths of a degree C.
@@ -542,7 +542,7 @@
  * Logs when the device turns off or on.
  *
  * Logged from:
-  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  */
 message DeviceOnStatusChanged {
     enum State {
@@ -556,7 +556,7 @@
  * Logs when an app's wakeup alarm fires.
  *
  * Logged from:
-  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  */
 message WakeupAlarmOccurred {
     // TODO: Add attribution instead of uid?
@@ -581,7 +581,7 @@
  * Logs wifi locks held by an app.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiLockStateChanged {
     // TODO: Add attribution instead of uid.
@@ -598,7 +598,7 @@
  * Logs wifi signal strength changes.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiSignalStrengthChanged {
     // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
@@ -616,7 +616,7 @@
  * Logs wifi scans performed by an app.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiScanStateChanged {
     // TODO: Add attribution instead of uid.
@@ -633,7 +633,7 @@
  * Logs phone signal strength changes.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message PhoneSignalStrengthChanged {
     // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
@@ -645,4 +645,37 @@
         SIGNAL_STRENGTH_GREAT = 4;
     }
     optional SignalStrength signal_strength = 1;
-}
\ No newline at end of file
+}
+
+/**
+ * Logs that a setting was updated.
+ * Logged from:
+ *   frameworks/base/core/java/android/provider/Settings.java
+ * The tag and is_default allow resetting of settings to default values based on the specified
+ * tag. See Settings#putString(ContentResolver, String, String, String, boolean) for more details.
+ */
+message SettingChanged {
+    // The name of the setting.
+    optional string setting = 1;
+
+    // The change being imposed on this setting. May represent a number, eg "3".
+    optional string value = 2;
+
+    // The new value of this setting. For most settings, this is same as value. For some settings,
+    // value is +X or -X where X represents an element in a set. For example, if the previous value
+    // is A,B,C and value is -B, then new_value is A,C and prev_value is A,B,C.
+    // The +/- feature is currently only used for location_providers_allowed.
+    optional string new_value = 3;
+
+    // The previous value of this setting.
+    optional string prev_value = 4;
+
+    // The tag used with the is_default for resetting sets of settings. This is generally null.
+    optional string tag = 5;
+
+    // 1 indicates that this setting with tag should be resettable.
+    optional int32 is_default = 6;
+
+    // The user ID associated. Defined in android/os/UserHandle.java
+    optional int32 user = 7;
+}
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index ec91509..66a31a5 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -98,15 +98,19 @@
 }
 
 message UidMapping {
-  message AppInfo {
-    optional string app = 1;
+  message PackageInfoSnapshot {
+    message PackageInfo {
+      optional string name = 1;
 
-    optional int32 version = 2;
+      optional int32 version = 2;
 
-    optional int32 uid = 3;
+      optional int32 uid = 3;
+    }
+    optional int64 timestamp_nanos = 1;
+
+    repeated PackageInfo package_info = 2;
   }
-
-  repeated AppInfo initial = 1;
+  repeated PackageInfoSnapshot snapshots = 1;
 
   message Change {
     optional bool deletion = 1;
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 39c1d59..a428752 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -40,6 +40,8 @@
 
 typedef std::string HashableDimensionKey;
 
+typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index a4d2421..87884b33 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -82,6 +82,8 @@
   optional bool count_nesting = 3 [default = true];
 
   optional string stop_all = 4;
+
+  repeated KeyMatcher dimension = 5;
 }
 
 message Condition {
@@ -148,29 +150,24 @@
 message DurationMetric {
   optional int64 metric_id = 1;
 
-  optional string start = 2;
-
-  optional string stop = 3;
-
-  optional string stop_all = 4;
+  optional string what = 2;
 
   enum AggregationType {
     DURATION_SUM = 1;
 
     DURATION_MAX_SPARSE = 2;
-    DURATION_MIN_SPARSE = 3;
   }
-  optional AggregationType type = 5;
+  optional AggregationType type = 3;
 
-  optional string predicate = 6;
+  optional string predicate = 4;
 
-  repeated KeyMatcher dimension = 7;
+  repeated KeyMatcher dimension = 5;
 
-  optional Bucket bucket = 8;
+  optional Bucket bucket = 6;
 
-  repeated Alert alerts = 9;
+  repeated Alert alerts = 7;
 
-  repeated EventConditionLink links = 10;
+  repeated EventConditionLink links = 8;
 
 }
 
@@ -210,14 +207,7 @@
   repeated EventConditionLink links = 8;
 
   enum Operation {
-    SUM_DIFF = 1;
-    MIN_DIFF = 2;
-    MAX_DIFF = 3;
-    SUM = 4;
-    MIN = 5;
-    MAX = 6;
-    FIRST = 7;
-    LAST = 8;
+    SUM = 1;
   }
   optional Operation operation = 9 [default = SUM];
 }
diff --git a/cmds/statsd/tests/MaxDurationTracker_test.cpp b/cmds/statsd/tests/MaxDurationTracker_test.cpp
new file mode 100644
index 0000000..ae8bf42
--- /dev/null
+++ b/cmds/statsd/tests/MaxDurationTracker_test.cpp
@@ -0,0 +1,115 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/MaxDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+    MOCK_METHOD2(
+            query,
+            ConditionState(const int conditionIndex,
+                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucketInfo> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("", true, bucketStartTimeNs, key1);
+    tracker.noteStop("", bucketStartTimeNs + 10);
+
+    tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStop("", bucketStartTimeNs + 40);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(20, buckets[0].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucketInfo> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
+
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].duration_nanos());
+    EXPECT_EQ((long long)bucketSizeNs, buckets[1].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    MaxDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index b000e13..2af17c2 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -124,6 +124,39 @@
     return config;
 }
 
+StatsdConfig buildDimensionMetricsWithMultiTags() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_VERY_LOW");
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(3);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_LOW");
+
+    LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher("BATTERY_VERY_LOW");
+    combination->add_matcher("BATTERY_VERY_VERY_LOW");
+
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    CountMetric* metric = config.add_count_metric();
+    metric->set_metric_id(3);
+    metric->set_what("BATTERY_LOW");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    return config;
+}
+
 StatsdConfig buildCircleConditions() {
     StatsdConfig config;
     config.set_config_id(12345L);
@@ -180,6 +213,21 @@
                                  trackerToConditionMap));
 }
 
+TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+    StatsdConfig config = buildDimensionMetricsWithMultiTags();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                  trackerToConditionMap));
+}
+
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
     StatsdConfig config = buildCircleMatchers();
     set<int> allTagIds;
diff --git a/cmds/statsd/tests/OringDurationTracker_test.cpp b/cmds/statsd/tests/OringDurationTracker_test.cpp
new file mode 100644
index 0000000..0b79819
--- /dev/null
+++ b/cmds/statsd/tests/OringDurationTracker_test.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/OringDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+    MOCK_METHOD2(
+            query,
+            ConditionState(const int conditionIndex,
+                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(OringDurationTrackerTest, TestDurationOverlap) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(durationTimeNs, buckets[0].duration_nanos());
+}
+
+TEST(OringDurationTrackerTest, TestDurationConditionChange) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index f9a90e4..671f6d4 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -13,13 +13,17 @@
 // limitations under the License.
 
 #include "packages/UidMap.h"
+#include "config/ConfigKey.h"
 
 #include <gtest/gtest.h>
 
 #include <stdio.h>
 
 using namespace android;
-using namespace android::os::statsd;
+
+namespace android {
+namespace os {
+namespace statsd {
 
 #ifdef __ANDROID__
 const string kApp1 = "app1.sharing.1";
@@ -64,6 +68,57 @@
     EXPECT_FALSE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
 }
+
+TEST(UidMapTest, TestClearingOutput) {
+    UidMap m;
+
+    ConfigKey config1(1, "config1");
+    ConfigKey config2(1, "config2");
+
+    m.OnConfigUpdated(config1);
+
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+    uids.push_back(1000);
+    uids.push_back(1000);
+    apps.push_back(String16(kApp1.c_str()));
+    apps.push_back(String16(kApp2.c_str()));
+    versions.push_back(4);
+    versions.push_back(5);
+    m.updateMap(1, uids, versions, apps);
+
+    UidMapping results = m.getOutput(2, config1);
+    EXPECT_EQ(1, results.snapshots_size());
+
+    // It should be cleared now
+    results = m.getOutput(3, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+
+    // Now add another configuration.
+    m.OnConfigUpdated(config2);
+    m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+    results = m.getOutput(6, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(1, results.changes_size());
+
+    // Now we still haven't been able to delete anything
+    m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+    results = m.getOutput(8, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(2, results.changes_size());
+
+    results = m.getOutput(9, config2);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(2, results.changes_size());
+    // At this point both should be cleared.
+    EXPECT_EQ(0, m.mOutput.snapshots_size());
+    EXPECT_EQ(0, m.mOutput.changes_size());
+}
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 076c2bc..a8863bf 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static android.os.Build.VERSION_CODES.O_MR1;
-
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.CallSuper;
@@ -136,6 +134,7 @@
 import java.util.HashMap;
 import java.util.List;
 
+
 /**
  * An activity is a single, focused thing that the user can do.  Almost all
  * activities interact with the user, so the Activity class takes care of
@@ -991,17 +990,6 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
 
-        if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
-            final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
-            final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
-            ta.recycle();
-
-            if (isTranslucentOrFloating) {
-                throw new IllegalStateException(
-                        "Only fullscreen opaque activities can request orientation");
-            }
-        }
-
         if (mLastNonConfigurationInstances != null) {
             mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
         }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 41667c4..837c00a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@
      */
     public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
 
-
     /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
@@ -1001,20 +1000,12 @@
      * Returns true if the activity's orientation is fixed.
      * @hide
      */
-    public boolean isFixedOrientation() {
+    boolean isFixedOrientation() {
         return isFixedOrientationLandscape() || isFixedOrientationPortrait()
                 || screenOrientation == SCREEN_ORIENTATION_LOCKED;
     }
 
     /**
-     * Returns true if the specified orientation is considered fixed.
-     * @hide
-     */
-    static public boolean isFixedOrientation(int orientation) {
-        return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
-    }
-
-    /**
      * Returns true if the activity's orientation is fixed to landscape.
      * @hide
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 62f4bf5..2d6a7b0 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -72,6 +72,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -1886,7 +1887,11 @@
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
                 IContentProvider cp = mProviderHolder.getProvider(cr);
+                String prevValue = getStringForUser(cr, name, userHandle);
                 cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+                String newValue = getStringForUser(cr, name, userHandle);
+                StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+                        makeDefault ? 1 : 0, userHandle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                 return false;
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 4500862..c44c8dd 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@
      * Defines the duration in milliseconds a user needs to hold down the
      * appropriate button to enable the accessibility shortcut once it's configured.
      */
-    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
 
     /**
      * Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 12649e1..d032a45 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 syntax = "proto2";
+
 option java_multiple_files = true;
 option java_outer_classname = "WakeupSourcesProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 message KernelWakeSources {
@@ -27,6 +29,8 @@
 
 // Next Tag: 11
 message WakeupSourceProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
+
     // Name of the event which triggers application processor
     optional string name = 1;
 
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index f82ea76..22b3d73 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,6 +18,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "PageTypeInfoProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 /*
@@ -61,6 +63,7 @@
 
 // Next tag: 9
 message BlockProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
 
     optional int32 node = 1;
 
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index ab6a6a3..4d62a60 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -18,6 +18,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "ProcrankProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 //Memory usage of running processes
@@ -31,6 +33,8 @@
 
 // Next Tag: 11
 message ProcessProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
+
     // ID of the process
     optional int32 pid = 1;
 
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
new file mode 100644
index 0000000..4f1d2d5
--- /dev/null
+++ b/libs/protoutil/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "libprotoutil",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+
+    srcs: [
+        "src/EncodedBuffer.cpp",
+        "src/ProtoOutputStream.cpp",
+        "src/protobuf.cpp",
+    ],
+
+    export_include_dirs: ["include"],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+}
diff --git a/libs/protoutil/Android.mk b/libs/protoutil/Android.mk
deleted file mode 100644
index 2a2b087..0000000
--- a/libs/protoutil/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libprotoutil
-
-LOCAL_CFLAGS := \
-        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
-
-LOCAL_SHARED_LIBRARIES := \
-        libcutils \
-        liblog \
-
-LOCAL_C_INCLUDES := \
-        $(LOCAL_PATH)/include
-
-LOCAL_SRC_FILES := \
-        src/EncodedBuffer.cpp \
-        src/ProtoOutputStream.cpp \
-        src/protobuf.cpp \
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index b8415b2..2155084 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -19,13 +19,59 @@
 
 #include <android/util/EncodedBuffer.h>
 
-#include <stdint.h>
 #include <string>
 
 namespace android {
 namespace util {
 
 /**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+/**
+ * Mask for the field types stored in a fieldId.  Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
+
+const uint64_t FIELD_TYPE_UNKNOWN  = 0;
+const uint64_t FIELD_TYPE_DOUBLE   = 1ULL << FIELD_TYPE_SHIFT;   // double, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FLOAT    = 2ULL << FIELD_TYPE_SHIFT;   // float, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_INT64    = 3ULL << FIELD_TYPE_SHIFT;   // int64, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT64 if negative
+                                                                 // values are likely.
+const uint64_t FIELD_TYPE_UINT64   = 4ULL << FIELD_TYPE_SHIFT;   // uint64, varint on the wire.
+const uint64_t FIELD_TYPE_INT32    = 5ULL << FIELD_TYPE_SHIFT;   // int32, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT32 if negative
+                                                                 // values are likely.
+const uint64_t FIELD_TYPE_FIXED64  = 6ULL << FIELD_TYPE_SHIFT;   // uint64, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FIXED32  = 7ULL << FIELD_TYPE_SHIFT;   // uint32, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_BOOL     = 8ULL << FIELD_TYPE_SHIFT;   // bool, varint on the wire.
+const uint64_t FIELD_TYPE_STRING   = 9ULL << FIELD_TYPE_SHIFT;   // UTF-8 text.
+const uint64_t FIELD_TYPE_GROUP    = 10ULL << FIELD_TYPE_SHIFT;  // Tag-delimited message.  Deprecated.
+const uint64_t FIELD_TYPE_MESSAGE  = 11ULL << FIELD_TYPE_SHIFT;  // Length-delimited message.
+
+const uint64_t FIELD_TYPE_BYTES    = 12ULL << FIELD_TYPE_SHIFT;  // Arbitrary byte array.
+const uint64_t FIELD_TYPE_UINT32   = 13ULL << FIELD_TYPE_SHIFT;  // uint32, varint on the wire
+const uint64_t FIELD_TYPE_ENUM     = 14ULL << FIELD_TYPE_SHIFT;  // Enum, varint on the wire
+const uint64_t FIELD_TYPE_SFIXED32 = 15ULL << FIELD_TYPE_SHIFT;  // int32, exactly four bytes on the wire
+const uint64_t FIELD_TYPE_SFIXED64 = 16ULL << FIELD_TYPE_SHIFT;  // int64, exactly eight bytes on the wire
+const uint64_t FIELD_TYPE_SINT32   = 17ULL << FIELD_TYPE_SHIFT;  // int32, ZigZag-encoded varint on the wire
+const uint64_t FIELD_TYPE_SINT64   = 18ULL << FIELD_TYPE_SHIFT;  // int64, ZigZag-encoded varint on the wire
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
+
+/**
  * Class to write to a protobuf stream.
  *
  * Each write method takes an ID code from the protoc generated classes
@@ -60,11 +106,12 @@
     void end(long long token);
 
     /**
-     * Flushes the protobuf data out to given fd.
+     * Flushes the protobuf data out to given fd. When the following functions are called,
+     * it is not able to write to ProtoOutputStream any more since the data is compact.
      */
-    size_t size();
-    EncodedBuffer::iterator data();
-    bool flush(int fd);
+    size_t size(); // Get the size of the serialized protobuf.
+    EncodedBuffer::iterator data(); // Get the reader apis of the data.
+    bool flush(int fd); // Flush data directly to a file descriptor.
 
     // Please don't use the following functions to dump protos unless you are familiar with protobuf encoding.
     void writeRawVarint(uint64_t varint);
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index b91e3db0..9d8ee729 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -18,58 +18,10 @@
 #include <android/util/protobuf.h>
 #include <android/util/ProtoOutputStream.h>
 #include <cutils/log.h>
-#include <cstring>
 
 namespace android {
 namespace util {
 
-/**
- * Position of the field type in a (long long) fieldId.
- */
-const uint64_t FIELD_TYPE_SHIFT = 32;
-
-/**
- * Mask for the field types stored in a fieldId.  Leaves a whole
- * byte for future expansion, even though there are currently only 17 types.
- */
-const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
-
-const uint64_t FIELD_TYPE_UNKNOWN  = 0;
-const uint64_t TYPE_DOUBLE         = 1ULL << FIELD_TYPE_SHIFT;   // double, exactly eight bytes on the wire.
-const uint64_t TYPE_FLOAT          = 2ULL << FIELD_TYPE_SHIFT;   // float, exactly four bytes on the wire.
-const uint64_t TYPE_INT64          = 3ULL << FIELD_TYPE_SHIFT;   // int64, varint on the wire.  Negative numbers
-                                                                 // take 10 bytes.  Use TYPE_SINT64 if negative
-                                                                 // values are likely.
-const uint64_t TYPE_UINT64         = 4ULL << FIELD_TYPE_SHIFT;   // uint64, varint on the wire.
-const uint64_t TYPE_INT32          = 5ULL << FIELD_TYPE_SHIFT;   // int32, varint on the wire.  Negative numbers
-                                                                 // take 10 bytes.  Use TYPE_SINT32 if negative
-                                                                 // values are likely.
-const uint64_t TYPE_FIXED64        = 6ULL << FIELD_TYPE_SHIFT;   // uint64, exactly eight bytes on the wire.
-const uint64_t TYPE_FIXED32        = 7ULL << FIELD_TYPE_SHIFT;   // uint32, exactly four bytes on the wire.
-const uint64_t TYPE_BOOL           = 8ULL << FIELD_TYPE_SHIFT;   // bool, varint on the wire.
-const uint64_t TYPE_STRING         = 9ULL << FIELD_TYPE_SHIFT;   // UTF-8 text.
-const uint64_t TYPE_GROUP          = 10ULL << FIELD_TYPE_SHIFT;  // Tag-delimited message.  Deprecated.
-const uint64_t TYPE_MESSAGE        = 11ULL << FIELD_TYPE_SHIFT;  // Length-delimited message.
-
-const uint64_t TYPE_BYTES          = 12ULL << FIELD_TYPE_SHIFT;  // Arbitrary byte array.
-const uint64_t TYPE_UINT32         = 13ULL << FIELD_TYPE_SHIFT;  // uint32, varint on the wire
-const uint64_t TYPE_ENUM           = 14ULL << FIELD_TYPE_SHIFT;  // Enum, varint on the wire
-const uint64_t TYPE_SFIXED32       = 15ULL << FIELD_TYPE_SHIFT;  // int32, exactly four bytes on the wire
-const uint64_t TYPE_SFIXED64       = 16ULL << FIELD_TYPE_SHIFT;  // int64, exactly eight bytes on the wire
-const uint64_t TYPE_SINT32         = 17ULL << FIELD_TYPE_SHIFT;  // int32, ZigZag-encoded varint on the wire
-const uint64_t TYPE_SINT64         = 18ULL << FIELD_TYPE_SHIFT;  // int64, ZigZag-encoded varint on the wire
-
-//
-// FieldId flags for whether the field is single, repeated or packed.
-// TODO: packed is not supported yet.
-//
-const uint64_t FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_UNKNOWN = 0;
-const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
-
 ProtoOutputStream::ProtoOutputStream()
         :mBuffer(),
          mCopyBegin(0),
@@ -90,18 +42,18 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
         default:
             ALOGW("Field type %d is not supported when writing double val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -116,18 +68,18 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
         default:
             ALOGW("Field type %d is not supported when writing float val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -142,20 +94,20 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
-        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
-        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
         default:
             ALOGW("Field type %d is not supported when writing int val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -170,20 +122,20 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
-        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
-        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
         default:
             ALOGW("Field type %d is not supported when writing long long val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -198,7 +150,7 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_BOOL:
+        case FIELD_TYPE_BOOL:
             writeBoolImpl(id, val);
             return true;
         default:
@@ -214,7 +166,7 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_STRING:
+        case FIELD_TYPE_STRING:
             writeUtf8StringImpl(id, val.c_str(), val.size());
             return true;
         default:
@@ -230,11 +182,11 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_STRING:
-        case TYPE_BYTES:
+        case FIELD_TYPE_STRING:
+        case FIELD_TYPE_BYTES:
             writeUtf8StringImpl(id, val, size);
             return true;
-        case TYPE_MESSAGE:
+        case FIELD_TYPE_MESSAGE:
             // can directly write valid format of message bytes into ProtoOutputStream without calling start/end
             writeMessageBytesImpl(id, val, size);
             return true;
@@ -291,7 +243,7 @@
 long long
 ProtoOutputStream::start(uint64_t fieldId)
 {
-    if ((fieldId & FIELD_TYPE_MASK) != TYPE_MESSAGE) {
+    if ((fieldId & FIELD_TYPE_MASK) != FIELD_TYPE_MESSAGE) {
         ALOGE("Can't call start for non-message type field: 0x%llx", (long long)fieldId);
         return 0;
     }
@@ -564,7 +516,6 @@
 inline void
 ProtoOutputStream::writeDoubleImpl(uint32_t id, double val)
 {
-    if (val == 0.0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val));
 }
@@ -572,7 +523,6 @@
 inline void
 ProtoOutputStream::writeFloatImpl(uint32_t id, float val)
 {
-    if (val == 0.0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val));
 }
@@ -580,7 +530,6 @@
 inline void
 ProtoOutputStream::writeInt64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64((uint64_t)val);
 }
@@ -588,7 +537,6 @@
 inline void
 ProtoOutputStream::writeInt32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32((uint32_t)val);
 }
@@ -596,7 +544,6 @@
 inline void
 ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val)
 {
-   if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64(val);
 }
@@ -604,7 +551,6 @@
 inline void
 ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32(val);
 }
@@ -612,7 +558,6 @@
 inline void
 ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64(val);
 }
@@ -620,7 +565,6 @@
 inline void
 ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32(val);
 }
@@ -628,7 +572,6 @@
 inline void
 ProtoOutputStream::writeSFixed64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64((uint64_t)val);
 }
@@ -636,7 +579,6 @@
 inline void
 ProtoOutputStream::writeSFixed32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32((uint32_t)val);
 }
@@ -644,7 +586,6 @@
 inline void
 ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64((val << 1) ^ (val >> 63));
 }
@@ -652,7 +593,6 @@
 inline void
 ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32((val << 1) ^ (val >> 31));
 }
@@ -667,7 +607,6 @@
 inline void
 ProtoOutputStream::writeBoolImpl(uint32_t id, bool val)
 {
-    if (!val) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32(val ? 1 : 0);
 }
@@ -675,7 +614,7 @@
 inline void
 ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size)
 {
-    if (val == NULL || size == 0) return;
+    if (val == NULL) return;
     writeLengthDelimitedHeader(id, size);
     for (size_t i=0; i<size; i++) {
         mBuffer.writeRawByte((uint8_t)val[i]);
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index ba41a7b..9175416 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@
                 });
             }
 
+            String hasImage = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
             String hasVideo = retriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
 
-            final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
-            if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
-                String width = retriever.extractMetadata(
+            String width = null;
+            String height = null;
+            String rotation = null;
+            final String METADATA_VALUE_YES = "yes";
+            // If the file has both image and video, prefer image info over video info.
+            // App querying ExifInterface is most likely using the bitmap path which
+            // picks the image first.
+            if (METADATA_VALUE_YES.equals(hasImage)) {
+                width = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+                height = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+                rotation = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+            } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+                width = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-                String height = retriever.extractMetadata(
+                height = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-
-                if (width != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
-                            ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
-                }
-
-                if (height != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
-                            ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
-                }
-
-                String rotation = retriever.extractMetadata(
+                rotation = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-                if (rotation != null) {
-                    int orientation = ExifInterface.ORIENTATION_NORMAL;
+            }
 
-                    // all rotation angles in CW
-                    switch (Integer.parseInt(rotation)) {
-                        case 90:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
-                            break;
-                        case 180:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_180;
-                            break;
-                        case 270:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
-                            break;
-                    }
+            if (width != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+                        ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+            }
 
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
-                            ExifAttribute.createUShort(orientation, mExifByteOrder));
+            if (height != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+                        ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+            }
+
+            if (rotation != null) {
+                int orientation = ExifInterface.ORIENTATION_NORMAL;
+
+                // all rotation angles in CW
+                switch (Integer.parseInt(rotation)) {
+                    case 90:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                        break;
+                    case 180:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_180;
+                        break;
+                    case 270:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                        break;
                 }
 
-                if (DEBUG) {
-                    Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
-                }
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+                        ExifAttribute.createUShort(orientation, mExifByteOrder));
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
             }
         } finally {
             retriever.release();
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index c2167f3..4658924 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -33,19 +33,17 @@
         android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
         android:clipToPadding="false"
-        android:layout_marginStart="4dp"
         android:paddingStart="?android:attr/listPreferredItemPaddingStart"
         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
         <LinearLayout
             android:id="@+id/icon_frame"
-            style="@style/preference_icon_frame"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="start|center_vertical"
+            android:minWidth="56dp"
             android:orientation="horizontal"
             android:clipToPadding="false"
-            android:paddingEnd="12dp"
             android:paddingTop="4dp"
             android:paddingBottom="4dp">
             <android.support.v7.internal.widget.PreferenceImageView
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index b7ea1d4..3f312f4 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -21,9 +21,4 @@
     <style name="TextAppearanceMedium">
         <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
     </style>
-
-    <style name="preference_icon_frame">
-        <item name="android:layout_marginStart">-4dp</item>
-        <item name="android:minWidth">60dp</item>
-    </style>
 </resources>
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 236fbfd..02a62e1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -112,4 +113,12 @@
         }
         return log;
     }
+
+    public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable String text) {
+        if (text == null) {
+            pw.println("null");
+        } else {
+            pw.print(text.length()); pw.println("_chars");
+        }
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 6d3d792..dac4586 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -49,6 +49,8 @@
 
 import com.android.internal.R;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
+
 import libcore.util.Objects;
 
 import java.io.PrintWriter;
@@ -466,7 +468,8 @@
         pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
         pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
         pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
-        pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
+        pw.print(prefix); pw.print("mFilterText: ");
+        Helper.printlnRedactedText(pw, mFilterText);
         pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
         pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2f0b649..481575e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -77,7 +77,6 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -886,6 +885,7 @@
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
+
         if (ent != null) {
             fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
             hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
@@ -2119,11 +2119,6 @@
     }
 
     void setRequestedOrientation(int requestedOrientation) {
-        if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
-                && appInfo.targetSdkVersion >= O_MR1) {
-            throw new IllegalStateException("Only fullscreen activities can request orientation");
-        }
-
         final int displayId = getDisplayId();
         final Configuration displayConfig =
                 mStackSupervisor.getDisplayOverrideConfiguration(displayId);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 22d2bcf..2219de8d8 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -144,15 +144,16 @@
     public final static class AppUpdateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             /**
              * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
              * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+             * If we can't find the value for EXTRA_REPLACING, we default to false.
              */
-            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
-                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
+                    && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                 return; // Keep only replacing or normal add and remove.
             }
+            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
                     Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e873d32..fd71d2f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -21,7 +21,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -1201,15 +1200,6 @@
      */
     @Override
     int getOrientation(int candidate) {
-        // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
-        // do throw an exception in {@link Activity#onCreate} and
-        // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
-        // other calculations aren't affected.
-        if (!fillsParent() && mTargetSdk >= O_MR1) {
-            // Can't specify orientation if app doesn't fill parent.
-            return SCREEN_ORIENTATION_UNSET;
-        }
-
         if (candidate == SCREEN_ORIENTATION_BEHIND) {
             // Allow app to specify orientation regardless of its visibility state if the current
             // candidate want us to use orientation behind. I.e. the visible app on-top of this one
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 9ad7add..ea4f4b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
         token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         token.setFillsParent(false);
-        // Can specify orientation if app doesn't fill parent. Allowed for SDK <= 25.
+        // Can specify orientation if app doesn't fill parent.
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
 
         token.setFillsParent(true);
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index f6006b0..6435ad9 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -15,9 +15,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.testables">
+    package="com.android.testables" android:sharedUserId="android.uid.system">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 756549c..96e060d 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -25,6 +25,25 @@
     ],
 }
 
+cc_library {
+    name: "streamingflags",
+    host_supported: true,
+    proto: {
+        export_proto_headers: true,
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    target: {
+        host: {
+            proto: {
+                type: "full",
+            },
+            srcs: [
+                "stream.proto",
+            ],
+        },
+    },
+}
 
 cc_binary_host {
     name: "protoc-gen-javastream",
@@ -44,4 +63,5 @@
 
     defaults: ["protoc-gen-stream-defaults"],
     shared_libs: ["libprotoc"],
+    static_libs: ["streamingflags"],
 }
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d4e1b7a..dc96d5c 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -1,6 +1,8 @@
 #include "Errors.h"
 #include "string_utils.h"
 
+#include <frameworks/base/tools/streaming_proto/stream.pb.h>
+
 #include "google/protobuf/compiler/plugin.pb.h"
 #include "google/protobuf/io/zero_copy_stream_impl.h"
 #include "google/protobuf/text_format.h"
@@ -160,6 +162,12 @@
     text << endl;
 }
 
+static inline bool
+should_generate_fields_mapping(const DescriptorProto& message)
+{
+    return message.options().GetExtension(stream).enable_fields_mapping();
+}
+
 static void
 write_message(stringstream& text, const DescriptorProto& message, const string& indent)
 {
@@ -167,8 +175,7 @@
     const string indented = indent + INDENT;
 
     text << indent << "// message " << message.name() << endl;
-    text << indent << "class " << message.name() << " {" << endl;
-    text << indent << "public:" << endl;
+    text << indent << "namespace " << message.name() << " {" << endl;
 
     // Enums
     N = message.enum_type_size();
@@ -188,12 +195,27 @@
         write_field(text, message.field(i), indented);
     }
 
-    text << indent << "};" << endl;
+    if (should_generate_fields_mapping(message)) {
+        N = message.field_size();
+        text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
+        text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+        for (int i=0; i<N; i++) {
+            text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
+        }
+        text << indented << "};" << endl;
+        text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+        for (int i=0; i<N; i++) {
+            text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
+        }
+        text << indented << "};" << endl << endl;
+    }
+
+    text << indent << "} //" << message.name() << endl;
     text << endl;
 }
 
 static void
-write_cpp_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
 {
     stringstream text;
 
@@ -255,7 +277,7 @@
     for (int i=0; i<N; i++) {
         const FileDescriptorProto& file_descriptor = request.proto_file(i);
         if (should_generate_for_file(request, file_descriptor.name())) {
-            write_cpp_file(&response, file_descriptor);
+            write_header_file(&response, file_descriptor);
         }
     }
 
@@ -270,4 +292,4 @@
 
     /* code */
     return 0;
-}
\ No newline at end of file
+}
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
new file mode 100644
index 0000000..123506c
--- /dev/null
+++ b/tools/streaming_proto/stream.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+package android.stream_proto;
+
+// This option tells streaming proto plugin to compile .proto files with extra features.
+message StreamFlags {
+  // creates a mapping of field names of the message to its field ids
+  optional bool enable_fields_mapping = 1;
+}
+
+extend google.protobuf.MessageOptions {
+    // Flags used by streaming proto plugins
+    optional StreamFlags stream = 126856794;
+}