Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)

Bug: 166295507
Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27
Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index 2adbc1f..7c30c8b 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -17,8 +17,8 @@
 package com.android.commands.am;
 
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
+import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
-import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
 
 import android.app.IActivityManager;
 import android.app.IInstrumentationWatcher;
@@ -512,7 +512,7 @@
                 flags |= INSTR_FLAG_DISABLE_TEST_API_CHECKS;
             }
             if (disableIsolatedStorage) {
-                flags |= INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
+                flags |= INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
             }
             if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
                         abi)) {
diff --git a/cmds/bmgr/TEST_MAPPING b/cmds/bmgr/TEST_MAPPING
new file mode 100644
index 0000000..7c0e79e
--- /dev/null
+++ b/cmds/bmgr/TEST_MAPPING
@@ -0,0 +1,11 @@
+{
+  "presubmit": [
+  ],
+  "postsubmit": [
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/backup"
+    }
+  ]
+}
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 680ccfc..ed717c4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -64,6 +64,10 @@
 
     private static final String BMGR_NOT_RUNNING_ERR =
             "Error: Could not access the Backup Manager.  Is the system running?";
+    private static final String BMGR_NOT_ACTIVATED_FOR_USER =
+            "Error: Backup Manager is not activated for user ";
+    private static final String BMGR_ERR_NO_RESTORESESSION_FOR_USER =
+            "Error: Could not get restore session for user ";
     private static final String TRANSPORT_NOT_RUNNING_ERR =
             "Error: Could not access the backup transport.  Is the system running?";
     private static final String PM_NOT_RUNNING_ERR =
@@ -121,6 +125,11 @@
             return;
         }
 
+        if ("autorestore".equals(op)) {
+            doAutoRestore(userId);
+            return;
+        }
+
         if ("enabled".equals(op)) {
             doEnabled(userId);
             return;
@@ -190,21 +199,45 @@
         showUsage();
     }
 
-    boolean isBackupActive(@UserIdInt int userId) {
+    private void handleRemoteException(RemoteException e) {
+        System.err.println(e.toString());
+        System.err.println(BMGR_NOT_RUNNING_ERR);
+    }
+
+    private boolean isBackupActive(@UserIdInt int userId) {
         try {
             if (!mBmgr.isBackupServiceActive(userId)) {
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                System.err.println(BMGR_NOT_ACTIVATED_FOR_USER + userId);
                 return false;
             }
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
             return false;
         }
 
         return true;
     }
 
+    private void doAutoRestore(int userId) {
+        String arg = nextArg();
+        if (arg == null) {
+            showUsage();
+            return;
+        }
+
+        try {
+            boolean enable = Boolean.parseBoolean(arg);
+            mBmgr.setAutoRestore(enable);
+            System.out.println(
+                    "Auto restore is now "
+                            + (enable ? "enabled" : "disabled")
+                            + " for user "
+                            + userId);
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+        }
+    }
+
     private String activatedToString(boolean activated) {
         return activated ? "activated" : "deactivated";
     }
@@ -214,8 +247,7 @@
             System.out.println("Backup Manager currently "
                     + activatedToString(mBmgr.isBackupServiceActive(userId)));
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
 
     }
@@ -230,8 +262,7 @@
             System.out.println("Backup Manager currently "
                     + enableToString(isEnabled));
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -250,8 +281,7 @@
             showUsage();
             return;
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -259,8 +289,7 @@
         try {
             mBmgr.backupNowForUser(userId);
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -274,8 +303,7 @@
         try {
             mBmgr.dataChangedForUser(userId, pkg);
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -292,8 +320,7 @@
                 mBmgr.fullTransportBackupForUser(
                         userId, allPkgs.toArray(new String[allPkgs.size()]));
             } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                handleRemoteException(e);
             }
         }
     }
@@ -421,8 +448,7 @@
             try {
                 filteredPackages = mBmgr.filterAppsEligibleForBackupForUser(userId, packages);
             } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                handleRemoteException(e);
             }
             backupNowPackages(userId, Arrays.asList(filteredPackages), nonIncrementalBackup,
                     monitorState);
@@ -455,8 +481,7 @@
                 System.err.println("Unable to run backup");
             }
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -506,8 +531,7 @@
             try {
                 mBmgr.cancelBackupsForUser(userId);
             } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                handleRemoteException(e);
             }
             return;
         }
@@ -537,8 +561,7 @@
             }
 
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -569,8 +592,7 @@
                         }
                     });
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
             return;
         }
 
@@ -598,8 +620,7 @@
             mBmgr.clearBackupDataForUser(userId, transport, pkg);
             System.out.println("Wiped backup data for " + pkg + " on " + transport);
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -632,8 +653,7 @@
             observer.waitForCompletion(30*1000L);
             System.out.println("Initialization result: " + observer.result);
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -648,7 +668,7 @@
         try {
             mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
             if (mRestore == null) {
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
             }
 
@@ -658,8 +678,7 @@
 
             mRestore.endRestoreSession();
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -686,8 +705,7 @@
                 System.out.println(pad + t);
             }
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -805,7 +823,7 @@
             boolean didRestore = false;
             mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
             if (mRestore == null) {
-                System.err.println(BMGR_NOT_RUNNING_ERR);
+                System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
             }
             RestoreSet[] sets = null;
@@ -851,8 +869,7 @@
 
             System.out.println("done");
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -865,8 +882,7 @@
                 }
             }
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -886,8 +902,7 @@
                             + " for user "
                             + userId);
         } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(BMGR_NOT_RUNNING_ERR);
+            handleRemoteException(e);
         }
     }
 
@@ -928,6 +943,7 @@
         System.err.println("       bmgr init TRANSPORT...");
         System.err.println("       bmgr activate BOOL");
         System.err.println("       bmgr activated");
+        System.err.println("       bmgr autorestore BOOL");
         System.err.println("");
         System.err.println("The '--user' option specifies the user on which the operation is run.");
         System.err.println("It must be the first argument before the operation.");
@@ -1002,6 +1018,9 @@
         System.err.println("");
         System.err.println("The 'activated' command reports the current activated/deactivated");
         System.err.println("state of the backup mechanism.");
+        System.err.println("");
+        System.err.println("The 'autorestore' command enables or disables automatic restore when");
+        System.err.println("a new package is installed.");
     }
 
     private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index c60d08b..757c2b2 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -43,6 +43,10 @@
     ],
 
     init_rc: ["bootanim.rc"],
+
+    cflags: [
+        "-Wno-deprecated-declarations",
+    ],
 }
 
 // libbootanimation
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 85db4b9..bb2de17 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -42,12 +42,13 @@
 
 #include <android-base/properties.h>
 
+#include <ui/DisplayConfig.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
-#include <ui/DisplayInfo.h>
 
 #include <gui/ISurfaceComposer.h>
+#include <gui/DisplayEventReceiver.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 
@@ -106,14 +107,15 @@
 static const int TEXT_CENTER_VALUE = INT_MAX;
 static const int TEXT_MISSING_VALUE = INT_MIN;
 static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
+static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 
 // ---------------------------------------------------------------------------
 
 BootAnimation::BootAnimation(sp<Callbacks> callbacks)
-        : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
-        mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
+        : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), mTimeFormat12Hour(false),
+        mTimeCheckThread(nullptr), mCallbacks(callbacks), mLooper(new Looper(false)) {
     mSession = new SurfaceComposerClient();
 
     std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
@@ -153,8 +155,7 @@
     return mSession;
 }
 
-void BootAnimation::binderDied(const wp<IBinder>&)
-{
+void BootAnimation::binderDied(const wp<IBinder>&) {
     // woah, surfaceflinger died!
     SLOGD("SurfaceFlinger died, exiting...");
 
@@ -218,8 +219,7 @@
     return NO_ERROR;
 }
 
-status_t BootAnimation::initTexture(FileMap* map, int* width, int* height)
-{
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
     SkBitmap bitmap;
     sk_sp<SkData> data = SkData::MakeWithoutCopy(map->getDataPtr(),
             map->getDataLength());
@@ -277,48 +277,173 @@
     return NO_ERROR;
 }
 
+class BootAnimation::DisplayEventCallback : public LooperCallback {
+    BootAnimation* mBootAnimation;
+
+public:
+    DisplayEventCallback(BootAnimation* bootAnimation) {
+        mBootAnimation = bootAnimation;
+    }
+
+    int handleEvent(int /* fd */, int events, void* /* data */) {
+        if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+            ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x",
+                    events);
+            return 0; // remove the callback
+        }
+
+        if (!(events & Looper::EVENT_INPUT)) {
+            ALOGW("Received spurious callback for unhandled poll event.  events=0x%x", events);
+            return 1; // keep the callback
+        }
+
+        constexpr int kBufferSize = 100;
+        DisplayEventReceiver::Event buffer[kBufferSize];
+        ssize_t numEvents;
+        do {
+            numEvents = mBootAnimation->mDisplayEventReceiver->getEvents(buffer, kBufferSize);
+            for (size_t i = 0; i < static_cast<size_t>(numEvents); i++) {
+                const auto& event = buffer[i];
+                if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
+                    SLOGV("Hotplug received");
+
+                    if (!event.hotplug.connected) {
+                        // ignore hotplug disconnect
+                        continue;
+                    }
+                    auto token = SurfaceComposerClient::getPhysicalDisplayToken(
+                        event.header.displayId);
+
+                    if (token != mBootAnimation->mDisplayToken) {
+                        // ignore hotplug of a secondary display
+                        continue;
+                    }
+
+                    DisplayConfig displayConfig;
+                    const status_t error = SurfaceComposerClient::getActiveDisplayConfig(
+                        mBootAnimation->mDisplayToken, &displayConfig);
+                    if (error != NO_ERROR) {
+                        SLOGE("Can't get active display configuration.");
+                    }
+                    mBootAnimation->resizeSurface(displayConfig.resolution.getWidth(),
+                        displayConfig.resolution.getHeight());
+                }
+            }
+        } while (numEvents > 0);
+
+        return 1;  // keep the callback
+    }
+};
+
+EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) {
+    const EGLint attribs[] = {
+        EGL_RED_SIZE,   8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE,  8,
+        EGL_DEPTH_SIZE, 0,
+        EGL_NONE
+    };
+    EGLint numConfigs;
+    EGLConfig config;
+    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
+    return config;
+}
+
+ui::Size BootAnimation::limitSurfaceSize(int width, int height) const {
+    ui::Size limited(width, height);
+    bool wasLimited = false;
+    const float aspectRatio = float(width) / float(height);
+    if (mMaxWidth != 0 && width > mMaxWidth) {
+        limited.height = mMaxWidth / aspectRatio;
+        limited.width = mMaxWidth;
+        wasLimited = true;
+    }
+    if (mMaxHeight != 0 && limited.height > mMaxHeight) {
+        limited.height = mMaxHeight;
+        limited.width = mMaxHeight * aspectRatio;
+        wasLimited = true;
+    }
+    SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]",
+             limited.width, limited.height, width, height);
+    return limited;
+}
+
 status_t BootAnimation::readyToRun() {
     mAssets.addDefaultAssets();
 
     mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
     if (mDisplayToken == nullptr)
-        return -1;
+        return NAME_NOT_FOUND;
 
-    DisplayInfo dinfo;
-    status_t status = SurfaceComposerClient::getDisplayInfo(mDisplayToken, &dinfo);
-    if (status)
-        return -1;
+    DisplayConfig displayConfig;
+    const status_t error =
+            SurfaceComposerClient::getActiveDisplayConfig(mDisplayToken, &displayConfig);
+    if (error != NO_ERROR)
+        return error;
 
+    mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
+    mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
+    ui::Size resolution = displayConfig.resolution;
+    resolution = limitSurfaceSize(resolution.width, resolution.height);
     // create the native surface
     sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
-            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
+            resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565);
 
     SurfaceComposerClient::Transaction t;
+
+    // this guest property specifies multi-display IDs to show the boot animation
+    // multiple ids can be set with comma (,) as separator, for example:
+    // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
+    Vector<uint64_t> physicalDisplayIds;
+    char displayValue[PROPERTY_VALUE_MAX] = "";
+    property_get(DISPLAYS_PROP_NAME, displayValue, "");
+    bool isValid = displayValue[0] != '\0';
+    if (isValid) {
+        char *p = displayValue;
+        while (*p) {
+            if (!isdigit(*p) && *p != ',') {
+                isValid = false;
+                break;
+            }
+            p ++;
+        }
+        if (!isValid)
+            SLOGE("Invalid syntax for the value of system prop: %s", DISPLAYS_PROP_NAME);
+    }
+    if (isValid) {
+        std::istringstream stream(displayValue);
+        for (PhysicalDisplayId id; stream >> id; ) {
+            physicalDisplayIds.add(id);
+            if (stream.peek() == ',')
+                stream.ignore();
+        }
+
+        // In the case of multi-display, boot animation shows on the specified displays
+        // in addition to the primary display
+        auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        constexpr uint32_t LAYER_STACK = 0;
+        for (auto id : physicalDisplayIds) {
+            if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
+                sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(id);
+                if (token != nullptr)
+                    t.setDisplayLayerStack(token, LAYER_STACK);
+            }
+        }
+        t.setLayerStack(control, LAYER_STACK);
+    }
+
     t.setLayer(control, 0x40000000)
         .apply();
 
     sp<Surface> s = control->getSurface();
 
     // initialize opengl and egl
-    const EGLint attribs[] = {
-            EGL_RED_SIZE,   8,
-            EGL_GREEN_SIZE, 8,
-            EGL_BLUE_SIZE,  8,
-            EGL_DEPTH_SIZE, 0,
-            EGL_NONE
-    };
-    EGLint w, h;
-    EGLint numConfigs;
-    EGLConfig config;
-    EGLSurface surface;
-    EGLContext context;
-
     EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-
     eglInitialize(display, nullptr, nullptr);
-    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
-    surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
-    context = eglCreateContext(display, config, nullptr, nullptr);
+    EGLConfig config = getEglConfig(display);
+    EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
+    EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
+    EGLint w, h;
     eglQuerySurface(display, surface, EGL_WIDTH, &w);
     eglQuerySurface(display, surface, EGL_HEIGHT, &h);
 
@@ -334,9 +459,47 @@
     mFlingerSurface = s;
     mTargetInset = -1;
 
+    // Register a display event receiver
+    mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
+    status_t status = mDisplayEventReceiver->initCheck();
+    SLOGE_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver failed with status: %d",
+            status);
+    mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT,
+            new DisplayEventCallback(this), nullptr);
+
     return NO_ERROR;
 }
 
+void BootAnimation::resizeSurface(int newWidth, int newHeight) {
+    // We assume this function is called on the animation thread.
+    if (newWidth == mWidth && newHeight == mHeight) {
+        return;
+    }
+    SLOGV("Resizing the boot animation surface to %d %d", newWidth, newHeight);
+
+    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    eglDestroySurface(mDisplay, mSurface);
+
+    const auto limitedSize = limitSurfaceSize(newWidth, newHeight);
+    mWidth = limitedSize.width;
+    mHeight = limitedSize.height;
+
+    SurfaceComposerClient::Transaction t;
+    t.setSize(mFlingerSurfaceControl, mWidth, mHeight);
+    t.apply();
+
+    EGLConfig config = getEglConfig(mDisplay);
+    EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr);
+    if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) {
+        SLOGE("Can't make the new surface current. Error %d", eglGetError());
+        return;
+    }
+    glViewport(0, 0, mWidth, mHeight);
+    glScissor(0, 0, mWidth, mHeight);
+
+    mSurface = surface;
+}
+
 bool BootAnimation::preloadAnimation() {
     findBootAnimationFile();
     if (!mZipFileName.isEmpty()) {
@@ -397,15 +560,14 @@
     }
 }
 
-bool BootAnimation::threadLoop()
-{
-    bool r;
+bool BootAnimation::threadLoop() {
+    bool result;
     // We have no bootanimation file, so we use the stock android logo
     // animation.
     if (mZipFileName.isEmpty()) {
-        r = android();
+        result = android();
     } else {
-        r = movie();
+        result = movie();
     }
 
     eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
@@ -416,11 +578,10 @@
     eglTerminate(mDisplay);
     eglReleaseThread();
     IPCThreadState::self()->stopProcess();
-    return r;
+    return result;
 }
 
-bool BootAnimation::android()
-{
+bool BootAnimation::android() {
     SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
             elapsedRealtime());
     initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
@@ -439,19 +600,19 @@
     glEnable(GL_TEXTURE_2D);
     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
-    const GLint xc = (mWidth  - mAndroid[0].w) / 2;
-    const GLint yc = (mHeight - mAndroid[0].h) / 2;
-    const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
-
-    glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
-            updateRect.height());
-
     // Blend state
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
     const nsecs_t startTime = systemTime();
     do {
+        processDisplayEvents();
+        const GLint xc = (mWidth  - mAndroid[0].w) / 2;
+        const GLint yc = (mHeight - mAndroid[0].h) / 2;
+        const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
+        glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
+                updateRect.height());
+
         nsecs_t now = systemTime();
         double time = now - startTime;
         float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
@@ -566,8 +727,7 @@
 }
 
 
-static bool readFile(ZipFileRO* zip, const char* name, String8& outString)
-{
+static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
     ZipEntryRO entry = zip->findEntryByName(name);
     SLOGE_IF(!entry, "couldn't find %s", name);
     if (!entry) {
@@ -688,8 +848,7 @@
     drawText(out, font, false, &x, &y);
 }
 
-bool BootAnimation::parseAnimationDesc(Animation& animation)
-{
+bool BootAnimation::parseAnimationDesc(Animation& animation)  {
     String8 desString;
 
     if (!readFile(animation.zip, "desc.txt", desString)) {
@@ -756,8 +915,7 @@
     return true;
 }
 
-bool BootAnimation::preloadZip(Animation& animation)
-{
+bool BootAnimation::preloadZip(Animation& animation) {
     // read all the data structures
     const size_t pcount = animation.parts.size();
     void *cookie = nullptr;
@@ -854,8 +1012,7 @@
     return true;
 }
 
-bool BootAnimation::movie()
-{
+bool BootAnimation::movie() {
     if (mAnimation == nullptr) {
         mAnimation = loadAnimation(mZipFileName);
     }
@@ -941,12 +1098,9 @@
     return false;
 }
 
-bool BootAnimation::playAnimation(const Animation& animation)
-{
+bool BootAnimation::playAnimation(const Animation& animation) {
     const size_t pcount = animation.parts.size();
     nsecs_t frameDuration = s2ns(1) / animation.fps;
-    const int animationX = (mWidth - animation.width) / 2;
-    const int animationY = (mHeight - animation.height) / 2;
 
     SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
             elapsedRealtime());
@@ -977,6 +1131,11 @@
                     1.0f);
 
             for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
+                processDisplayEvents();
+
+                const int animationX = (mWidth - animation.width) / 2;
+                const int animationY = (mHeight - animation.height) / 2;
+
                 const Animation::Frame& frame(part.frames[j]);
                 nsecs_t lastFrame = systemTime();
 
@@ -1060,6 +1219,12 @@
     return true;
 }
 
+void BootAnimation::processDisplayEvents() {
+    // This will poll mDisplayEventReceiver and if there are new events it'll call
+    // displayEventCallback synchronously.
+    mLooper->pollOnce(0);
+}
+
 void BootAnimation::handleViewport(nsecs_t timestep) {
     if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) {
         return;
@@ -1092,7 +1257,7 @@
         SurfaceComposerClient::Transaction t;
         t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
                 .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
-        t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect);
+        t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect);
         t.apply();
 
         mTargetInset = mCurrentInset = 0;
@@ -1102,8 +1267,7 @@
     mCurrentInset += delta;
 }
 
-void BootAnimation::releaseAnimation(Animation* animation) const
-{
+void BootAnimation::releaseAnimation(Animation* animation) const {
     for (Vector<Animation::Part>::iterator it = animation->parts.begin(),
          e = animation->parts.end(); it != e; ++it) {
         if (it->animation)
@@ -1114,8 +1278,7 @@
     delete animation;
 }
 
-BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
-{
+BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {
     if (mLoadedFiles.indexOf(fn) >= 0) {
         SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
             fn.string());
@@ -1136,10 +1299,10 @@
 
     parseAnimationDesc(*animation);
     if (!preloadZip(*animation)) {
+        releaseAnimation(animation);
         return nullptr;
     }
 
-
     mLoadedFiles.remove(fn);
     return animation;
 }
@@ -1260,7 +1423,7 @@
     if (mSystemWd < 0) {
         close(mInotifyFd);
         mInotifyFd = -1;
-        SLOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
+        SLOGE("Could not add watch for %s: %s", SYSTEM_DATA_DIR_PATH, strerror(errno));
         return NO_INIT;
     }
 
@@ -1277,5 +1440,4 @@
 
 // ---------------------------------------------------------------------------
 
-}
-; // namespace android
+} // namespace android
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 574d65e..6ba7fd4 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -18,11 +18,14 @@
 #define ANDROID_BOOTANIMATION_H
 
 #include <vector>
+#include <queue>
 
 #include <stdint.h>
 #include <sys/types.h>
 
 #include <androidfw/AssetManager.h>
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Looper.h>
 #include <utils/Thread.h>
 #include <binder/IBinder.h>
 
@@ -145,6 +148,11 @@
         BootAnimation* mBootAnimation;
     };
 
+    // Display event handling
+    class DisplayEventCallback;
+    int displayEventCallback(int fd, int events, void* data);
+    void processDisplayEvents();
+
     status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
     status_t initTexture(FileMap* map, int* width, int* height);
     status_t initFont(Font* font, const char* fallback);
@@ -161,6 +169,9 @@
     void findBootAnimationFile();
     bool findBootAnimationFileInternal(const std::vector<std::string>& files);
     bool preloadAnimation();
+    EGLConfig getEglConfig(const EGLDisplay&);
+    ui::Size limitSurfaceSize(int width, int height) const;
+    void resizeSurface(int newWidth, int newHeight);
 
     void checkExit();
 
@@ -171,6 +182,8 @@
     Texture     mAndroid[2];
     int         mWidth;
     int         mHeight;
+    int         mMaxWidth = 0;
+    int         mMaxHeight = 0;
     int         mCurrentInset;
     int         mTargetInset;
     bool        mUseNpotTextures = false;
@@ -189,6 +202,8 @@
     sp<TimeCheckThread> mTimeCheckThread = nullptr;
     sp<Callbacks> mCallbacks;
     Animation* mAnimation = nullptr;
+    std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
+    sp<Looper> mLooper;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 55dbc17..ca1d598 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -32,8 +32,11 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This class is a command line utility for manipulating content. A client
@@ -72,7 +75,7 @@
             "usage: adb shell content [subcommand] [options]\n"
                     + "\n"
                     + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]"
-                    + " --bind <BINDING> [--bind <BINDING>...]\n"
+                    + " --bind <BINDING> [--bind <BINDING>...] [--extra <BINDING>...]\n"
                     + "  <URI> a content provider URI.\n"
                     + "  <BINDING> binds a typed value to a column and is formatted:\n"
                     + "  <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n"
@@ -84,7 +87,8 @@
                     + "  adb shell content insert --uri content://settings/secure --bind name:s:new_setting"
                     + " --bind value:s:new_value\n"
                     + "\n"
-                    + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n"
+                    + "usage: adb shell content update --uri <URI> [--user <USER_ID>]"
+                    + " [--where <WHERE>] [--extra <BINDING>...]\n"
                     + "  <WHERE> is a SQL style where clause in quotes (You have to escape single quotes"
                     + " - see example below).\n"
                     + "  Example:\n"
@@ -93,14 +97,15 @@
                     + " value:s:newer_value --where \"name=\'new_setting\'\"\n"
                     + "\n"
                     + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>"
-                    + " [--bind <BINDING>...] [--where <WHERE>]\n"
+                    + " [--bind <BINDING>...] [--where <WHERE>] [--extra <BINDING>...]\n"
                     + "  Example:\n"
                     + "  # Remove \"new_setting\" secure setting.\n"
                     + "  adb shell content delete --uri content://settings/secure "
                     + "--where \"name=\'new_setting\'\"\n"
                     + "\n"
                     + "usage: adb shell content query --uri <URI> [--user <USER_ID>]"
-                    + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n"
+                    + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]"
+                    + " [--extra <BINDING>...]\n"
                     + "  <PROJECTION> is a list of colon separated column names and is formatted:\n"
                     + "  <COLUMN_NAME>[:<COLUMN_NAME>...]\n"
                     + "  <SORT_ORDER> is the order in which rows in the result should be sorted.\n"
@@ -196,6 +201,7 @@
             Uri uri = null;
             int userId = UserHandle.USER_SYSTEM;
             ContentValues values = new ContentValues();
+            Bundle extras = new Bundle();
             for (String argument; (argument = mTokenizer.nextArg()) != null;) {
                 if (ARGUMENT_URI.equals(argument)) {
                     uri = Uri.parse(argumentValueRequired(argument));
@@ -203,6 +209,8 @@
                     userId = Integer.parseInt(argumentValueRequired(argument));
                 } else if (ARGUMENT_BIND.equals(argument)) {
                     parseBindValue(values);
+                } else if (ARGUMENT_EXTRA.equals(argument)) {
+                    parseBindValue(extras);
                 } else {
                     throw new IllegalArgumentException("Unsupported argument: " + argument);
                 }
@@ -215,20 +223,23 @@
                 throw new IllegalArgumentException("Bindings not specified."
                         + " Did you specify --bind argument(s)?");
             }
-            return new InsertCommand(uri, userId, values);
+            return new InsertCommand(uri, userId, values, extras);
         }
 
         private DeleteCommand parseDeleteCommand() {
             Uri uri = null;
             int userId = UserHandle.USER_SYSTEM;
-            String where = null;
+            Bundle extras = new Bundle();
             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
                 if (ARGUMENT_URI.equals(argument)) {
                     uri = Uri.parse(argumentValueRequired(argument));
                 } else if (ARGUMENT_USER.equals(argument)) {
                     userId = Integer.parseInt(argumentValueRequired(argument));
                 } else if (ARGUMENT_WHERE.equals(argument)) {
-                    where = argumentValueRequired(argument);
+                    extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                            argumentValueRequired(argument));
+                } else if (ARGUMENT_EXTRA.equals(argument)) {
+                    parseBindValue(extras);
                 } else {
                     throw new IllegalArgumentException("Unsupported argument: " + argument);
                 }
@@ -237,23 +248,26 @@
                 throw new IllegalArgumentException("Content provider URI not specified."
                         + " Did you specify --uri argument?");
             }
-            return new DeleteCommand(uri, userId, where);
+            return new DeleteCommand(uri, userId, extras);
         }
 
         private UpdateCommand parseUpdateCommand() {
             Uri uri = null;
             int userId = UserHandle.USER_SYSTEM;
-            String where = null;
             ContentValues values = new ContentValues();
+            Bundle extras = new Bundle();
             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
                 if (ARGUMENT_URI.equals(argument)) {
                     uri = Uri.parse(argumentValueRequired(argument));
                 } else if (ARGUMENT_USER.equals(argument)) {
                     userId = Integer.parseInt(argumentValueRequired(argument));
                 } else if (ARGUMENT_WHERE.equals(argument)) {
-                    where = argumentValueRequired(argument);
+                    extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                            argumentValueRequired(argument));
                 } else if (ARGUMENT_BIND.equals(argument)) {
                     parseBindValue(values);
+                } else if (ARGUMENT_EXTRA.equals(argument)) {
+                    parseBindValue(extras);
                 } else {
                     throw new IllegalArgumentException("Unsupported argument: " + argument);
                 }
@@ -266,7 +280,7 @@
                 throw new IllegalArgumentException("Bindings not specified."
                         + " Did you specify --bind argument(s)?");
             }
-            return new UpdateCommand(uri, userId, values, where);
+            return new UpdateCommand(uri, userId, values, extras);
         }
 
         public CallCommand parseCallCommand() {
@@ -274,7 +288,7 @@
             int userId = UserHandle.USER_SYSTEM;
             String arg = null;
             Uri uri = null;
-            ContentValues values = new ContentValues();
+            Bundle extras = new Bundle();
             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
                 if (ARGUMENT_URI.equals(argument)) {
                     uri = Uri.parse(argumentValueRequired(argument));
@@ -285,11 +299,10 @@
                 } else if (ARGUMENT_ARG.equals(argument)) {
                     arg = argumentValueRequired(argument);
                 } else if (ARGUMENT_EXTRA.equals(argument)) {
-                    parseBindValue(values);
+                    parseBindValue(extras);
                 } else {
                     throw new IllegalArgumentException("Unsupported argument: " + argument);
                 }
-
             }
             if (uri == null) {
                 throw new IllegalArgumentException("Content provider URI not specified."
@@ -298,7 +311,7 @@
             if (method == null) {
                 throw new IllegalArgumentException("Content provider method not specified.");
             }
-            return new CallCommand(uri, userId, method, arg, values);
+            return new CallCommand(uri, userId, method, arg, extras);
         }
 
         private GetTypeCommand parseGetTypeCommand() {
@@ -363,19 +376,22 @@
             Uri uri = null;
             int userId = UserHandle.USER_SYSTEM;
             String[] projection = null;
-            String sort = null;
-            String where = null;
+            Bundle extras = new Bundle();
             for (String argument; (argument = mTokenizer.nextArg())!= null;) {
                 if (ARGUMENT_URI.equals(argument)) {
                     uri = Uri.parse(argumentValueRequired(argument));
                 } else if (ARGUMENT_USER.equals(argument)) {
                     userId = Integer.parseInt(argumentValueRequired(argument));
                 } else if (ARGUMENT_WHERE.equals(argument)) {
-                    where = argumentValueRequired(argument);
+                    extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                            argumentValueRequired(argument));
                 } else if (ARGUMENT_SORT.equals(argument)) {
-                    sort = argumentValueRequired(argument);
+                    extras.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
+                            argumentValueRequired(argument));
                 } else if (ARGUMENT_PROJECTION.equals(argument)) {
                     projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*");
+                } else if (ARGUMENT_EXTRA.equals(argument)) {
+                    parseBindValue(extras);
                 } else {
                     throw new IllegalArgumentException("Unsupported argument: " + argument);
                 }
@@ -384,40 +400,76 @@
                 throw new IllegalArgumentException("Content provider URI not specified."
                         + " Did you specify --uri argument?");
             }
-            return new QueryCommand(uri, userId, projection, where, sort);
+            return new QueryCommand(uri, userId, projection, extras);
         }
 
-        private void parseBindValue(ContentValues values) {
+        private List<String> splitWithEscaping(String argument, char splitChar) {
+            final List<String> res = new ArrayList<>();
+            final StringBuilder cur = new StringBuilder();
+            for (int i = 0; i < argument.length(); i++) {
+                char c = argument.charAt(i);
+                if (c == '\\') {
+                    if (++i == argument.length()) {
+                        throw new IllegalArgumentException("Invalid escaping");
+                    } else {
+                        // Skip escaping char and insert next
+                        c = argument.charAt(i);
+                        cur.append(c);
+                    }
+                } else if (c == splitChar) {
+                    // Splitting char means next string
+                    res.add(cur.toString());
+                    cur.setLength(0);
+                } else {
+                    // Copy non-escaping and non-splitting char
+                    cur.append(c);
+                }
+            }
+            res.add(cur.toString());
+            return res;
+        }
+
+        private Pair<String, Object> parseBindValue() {
             String argument = mTokenizer.nextArg();
             if (TextUtils.isEmpty(argument)) {
                 throw new IllegalArgumentException("Binding not well formed: " + argument);
             }
-            final int firstColonIndex = argument.indexOf(COLON);
-            if (firstColonIndex < 0) {
+            final List<String> split = splitWithEscaping(argument, COLON.charAt(0));
+            if (split.size() != 3) {
                 throw new IllegalArgumentException("Binding not well formed: " + argument);
             }
-            final int secondColonIndex = argument.indexOf(COLON, firstColonIndex + 1);
-            if (secondColonIndex < 0) {
-                throw new IllegalArgumentException("Binding not well formed: " + argument);
-            }
-            String column = argument.substring(0, firstColonIndex);
-            String type = argument.substring(firstColonIndex + 1, secondColonIndex);
-            String value = argument.substring(secondColonIndex + 1);
+            String column = split.get(0);
+            String type = split.get(1);
+            String value = split.get(2);
             if (TYPE_STRING.equals(type)) {
-                values.put(column, value);
+                return Pair.create(column, value);
             } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) {
-                values.put(column, Boolean.parseBoolean(value));
-            } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) {
-                values.put(column, Long.parseLong(value));
-            } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) {
-                values.put(column, Double.parseDouble(value));
+                return Pair.create(column, Boolean.parseBoolean(value));
+            } else if (TYPE_INTEGER.equalsIgnoreCase(type)) {
+                return Pair.create(column, Integer.parseInt(value));
+            } else if (TYPE_LONG.equalsIgnoreCase(type)) {
+                return Pair.create(column, Long.parseLong(value));
+            } else if (TYPE_FLOAT.equalsIgnoreCase(type)) {
+                return Pair.create(column, Float.parseFloat(value));
+            } else if (TYPE_DOUBLE.equalsIgnoreCase(type)) {
+                return Pair.create(column, Double.parseDouble(value));
             } else if (TYPE_NULL.equalsIgnoreCase(type)) {
-                values.putNull(column);
+                return Pair.create(column, null);
             } else {
                 throw new IllegalArgumentException("Unsupported type: " + type);
             }
         }
 
+        private void parseBindValue(ContentValues values) {
+            final Pair<String, Object> columnValue = parseBindValue();
+            values.putObject(columnValue.first, columnValue.second);
+        }
+
+        private void parseBindValue(Bundle extras) {
+            final Pair<String, Object> columnValue = parseBindValue();
+            extras.putObject(columnValue.first, columnValue.second);
+        }
+
         private String argumentValueRequired(String argument) {
             String value = mTokenizer.nextArg();
             if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) {
@@ -500,64 +552,48 @@
 
     private static class InsertCommand extends Command {
         final ContentValues mContentValues;
+        final Bundle mExtras;
 
-        public InsertCommand(Uri uri, int userId, ContentValues contentValues) {
+        public InsertCommand(Uri uri, int userId, ContentValues contentValues, Bundle extras) {
             super(uri, userId);
             mContentValues = contentValues;
+            mExtras = extras;
         }
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.insert(resolveCallingPackage(), mUri, mContentValues);
+            provider.insert(resolveCallingPackage(), null, mUri, mContentValues, mExtras);
         }
     }
 
     private static class DeleteCommand extends Command {
-        final String mWhere;
+        final Bundle mExtras;
 
-        public DeleteCommand(Uri uri, int userId, String where) {
+        public DeleteCommand(Uri uri, int userId, Bundle extras) {
             super(uri, userId);
-            mWhere = where;
+            mExtras = extras;
         }
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.delete(resolveCallingPackage(), mUri, mWhere, null);
+            provider.delete(resolveCallingPackage(), null, mUri, mExtras);
         }
     }
 
     private static class CallCommand extends Command {
         final String mMethod, mArg;
-        Bundle mExtras = null;
+        final Bundle mExtras;
 
-        public CallCommand(Uri uri, int userId, String method, String arg, ContentValues values) {
+        public CallCommand(Uri uri, int userId, String method, String arg, Bundle extras) {
             super(uri, userId);
             mMethod = method;
             mArg = arg;
-            if (values != null) {
-                mExtras = new Bundle();
-                for (String key : values.keySet()) {
-                    final Object val = values.get(key);
-                    if (val instanceof String) {
-                        mExtras.putString(key, (String) val);
-                    } else if (val instanceof Float) {
-                        mExtras.putFloat(key, (Float) val);
-                    } else if (val instanceof Double) {
-                        mExtras.putDouble(key, (Double) val);
-                    } else if (val instanceof Boolean) {
-                        mExtras.putBoolean(key, (Boolean) val);
-                    } else if (val instanceof Integer) {
-                        mExtras.putInt(key, (Integer) val);
-                    } else if (val instanceof Long) {
-                        mExtras.putLong(key, (Long) val);
-                    }
-                }
-            }
+            mExtras = extras;
         }
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            Bundle result = provider.call(null, mUri.getAuthority(), mMethod, mArg, mExtras);
+            Bundle result = provider.call(null, null, mUri.getAuthority(), mMethod, mArg, mExtras);
             if (result != null) {
                 result.size(); // unpack
             }
@@ -584,7 +620,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) {
+            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "r", null, null)) {
                 FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out);
             }
         }
@@ -597,27 +633,26 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) {
+            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "w", null, null)) {
                 FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor());
             }
         }
     }
 
-    private static class QueryCommand extends DeleteCommand {
+    private static class QueryCommand extends Command {
         final String[] mProjection;
-        final String mSortOrder;
+        final Bundle mExtras;
 
-        public QueryCommand(
-                Uri uri, int userId, String[] projection, String where, String sortOrder) {
-            super(uri, userId, where);
+        public QueryCommand(Uri uri, int userId, String[] projection, Bundle extras) {
+            super(uri, userId);
             mProjection = projection;
-            mSortOrder = sortOrder;
+            mExtras = extras;
         }
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection,
-                    ContentResolver.createSqlQueryBundle(mWhere, null, mSortOrder), null);
+            Cursor cursor = provider.query(resolveCallingPackage(), null, mUri, mProjection,
+                    mExtras, null);
             if (cursor == null) {
                 System.out.println("No result found.");
                 return;
@@ -669,17 +704,19 @@
         }
     }
 
-    private static class UpdateCommand extends InsertCommand {
-        final String mWhere;
+    private static class UpdateCommand extends Command {
+        final ContentValues mValues;
+        final Bundle mExtras;
 
-        public UpdateCommand(Uri uri, int userId, ContentValues contentValues, String where) {
-            super(uri, userId, contentValues);
-            mWhere = where;
+        public UpdateCommand(Uri uri, int userId, ContentValues values, Bundle extras) {
+            super(uri, userId);
+            mValues = values;
+            mExtras = extras;
         }
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.update(resolveCallingPackage(), mUri, mContentValues, mWhere, null);
+            provider.update(resolveCallingPackage(), null, mUri, mValues, mExtras);
         }
     }
 
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index 6c6797a..d0c2a24 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -48,8 +48,8 @@
     private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record";
     private static final String COMMAND_FORCE_NETWORK_LOGS = "force-network-logs";
     private static final String COMMAND_FORCE_SECURITY_LOGS = "force-security-logs";
-    private static final String COMMAND_GRANT_PO_DEVICE_ID_ACCESS =
-            "grant-profile-owner-device-ids-access";
+    private static final String COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE =
+            "mark-profile-owner-on-organization-owned-device";
 
     private IDevicePolicyManager mDevicePolicyManager;
     private int mUserId = UserHandle.USER_SYSTEM;
@@ -93,7 +93,7 @@
                 "dpm " + COMMAND_FORCE_SECURITY_LOGS + ": makes all security logs available to " +
                 "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed."
                 + "\n"
-                + "usage: dpm " + COMMAND_GRANT_PO_DEVICE_ID_ACCESS + ": "
+                + "usage: dpm " + COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE + ": "
                 + "[ --user <USER_ID> | current ] <COMPONENT>\n");
     }
 
@@ -129,8 +129,8 @@
             case COMMAND_FORCE_SECURITY_LOGS:
                 runForceSecurityLogs();
                 break;
-            case COMMAND_GRANT_PO_DEVICE_ID_ACCESS:
-                runGrantProfileOwnerDeviceIdsAccess();
+            case COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE:
+                runMarkProfileOwnerOnOrganizationOwnedDevice();
                 break;
             default:
                 throw new IllegalArgumentException ("unknown command '" + command + "'");
@@ -251,9 +251,9 @@
     }
 
 
-    private void runGrantProfileOwnerDeviceIdsAccess() throws RemoteException {
+    private void runMarkProfileOwnerOnOrganizationOwnedDevice() throws RemoteException {
         parseArgs(/*canHaveName=*/ false);
-        mDevicePolicyManager.grantDeviceIdsAccessToProfileOwner(mComponent, mUserId);
+        mDevicePolicyManager.markProfileOwnerOnOrganizationOwnedDevice(mComponent, mUserId);
         System.out.println("Success");
     }
 
diff --git a/cmds/hid/README.md b/cmds/hid/README.md
index 7e22d08..620336f 100644
--- a/cmds/hid/README.md
+++ b/cmds/hid/README.md
@@ -38,17 +38,21 @@
 Register a new uhid device
 
 | Field         | Type          | Description                |
-|:-------------:|:-------------:|:--------------------------|
+|:-------------:|:-------------:|:-------------------------- |
 | id            | integer       | Device id                  |
 | command       | string        | Must be set to "register"  |
 | name          | string        | Device name                |
 | vid           | 16-bit integer| Vendor id                  |
 | pid           | 16-bit integer| Product id                 |
+| bus           | string        | Bus that device should use |
 | descriptor    | byte array    | USB HID report descriptor  |
 
 Device ID is used for matching the subsequent commands to a specific device
 to avoid ambiguity when multiple devices are registered.
 
+Device bus is used to determine how the uhid device is connected to the host.
+The options are "usb" and "bluetooth".
+
 USB HID report descriptor should be generated according the the USB HID spec
 and can be checked by reverse parsing using a variety of tools, for example
 [usbdescreqparser][5].
@@ -61,6 +65,7 @@
   "name": "Odie (Test)",
   "vid": 0x18d1,
   "pid": 0x2c40,
+  "bus": "usb",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
     0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
     0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
@@ -142,4 +147,4 @@
 [3]: ../../../../cts/tests/tests/hardware/res/raw/
 [4]: https://developer.android.com/training/game-controllers/controller-input.html#button
 [5]: http://eleccelerator.com/usbdescreqparser/
-[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file
+[6]: https://developer.android.com/training/game-controllers/controller-input.html
diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp
index 095cfc6..2c07de0 100644
--- a/cmds/hid/jni/Android.bp
+++ b/cmds/hid/jni/Android.bp
@@ -5,6 +5,7 @@
 
     shared_libs: [
         "libandroid",
+        "libbase",
         "liblog",
         "libnativehelper",
     ],
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index d4fdf85..1e200c5 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -21,17 +21,21 @@
 #include <linux/uhid.h>
 
 #include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
 #include <cstdio>
 #include <cstring>
 #include <memory>
-#include <unistd.h>
 
+#include <android/log.h>
+#include <android/looper.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <android/looper.h>
-#include <android/log.h>
+
+#include <android-base/stringprintf.h>
 
 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 #define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
@@ -46,6 +50,7 @@
 static struct {
     jmethodID onDeviceOpen;
     jmethodID onDeviceGetReport;
+    jmethodID onDeviceOutput;
     jmethodID onDeviceError;
 } gDeviceCallbackClassInfo;
 
@@ -61,6 +66,26 @@
     }
 }
 
+static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::vector<uint8_t>& vector) {
+    ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(vector.size()));
+    if (array.get() == nullptr) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return array;
+    }
+    static_assert(sizeof(char) == sizeof(uint8_t));
+    env->SetByteArrayRegion(array.get(), 0, vector.size(),
+                            reinterpret_cast<const signed char*>(vector.data()));
+    return array;
+}
+
+static std::string toString(const std::vector<uint8_t>& data) {
+    std::string s = "";
+    for (uint8_t b : data) {
+        s += android::base::StringPrintf("%x ", b);
+    }
+    return s;
+}
+
 DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) :
     mCallbackObject(env->NewGlobalRef(callback)) {
     env->GetJavaVM(&mJavaVM);
@@ -90,23 +115,30 @@
     checkAndClearException(env, "onDeviceGetReport");
 }
 
+void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) {
+    JNIEnv* env = getJNIEnv();
+    env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput,
+                        toJbyteArray(env, data).get());
+    checkAndClearException(env, "onDeviceOutput");
+}
+
 JNIEnv* DeviceCallback::getJNIEnv() {
     JNIEnv* env;
     mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
     return env;
 }
 
-Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
-        std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback) {
-
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
+                                     uint16_t bus, const std::vector<uint8_t>& descriptor,
+                                     std::unique_ptr<DeviceCallback> callback) {
     size_t size = descriptor.size();
     if (size > HID_MAX_DESCRIPTOR_SIZE) {
         LOGE("Received invalid hid report with descriptor size %zu, skipping", size);
         return nullptr;
     }
 
-    int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC);
-    if (fd < 0) {
+    android::base::unique_fd fd(::open(UHID_PATH, O_RDWR | O_CLOEXEC));
+    if (!fd.ok()) {
         LOGE("Failed to open uhid: %s", strerror(errno));
         return nullptr;
     }
@@ -114,10 +146,11 @@
     struct uhid_event ev = {};
     ev.type = UHID_CREATE2;
     strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
-    memcpy(&ev.u.create2.rd_data, descriptor.data(),
-            size * sizeof(ev.u.create2.rd_data[0]));
+    std::string uniq = android::base::StringPrintf("Id: %d", id);
+    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
+    memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
     ev.u.create2.rd_size = size;
-    ev.u.create2.bus = BUS_BLUETOOTH;
+    ev.u.create2.bus = bus;
     ev.u.create2.vendor = vid;
     ev.u.create2.product = pid;
     ev.u.create2.version = 0;
@@ -126,7 +159,6 @@
     errno = 0;
     ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
     if (ret < 0 || ret != sizeof(ev)) {
-        ::close(fd);
         LOGE("Failed to create uhid node: %s", strerror(errno));
         return nullptr;
     }
@@ -134,21 +166,21 @@
     // Wait for the device to actually be created.
     ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev)));
     if (ret < 0 || ev.type != UHID_START) {
-        ::close(fd);
         LOGE("uhid node failed to start: %s", strerror(errno));
         return nullptr;
     }
-    return new Device(id, fd, std::move(callback));
+    // using 'new' to access non-public constructor
+    return std::unique_ptr<Device>(new Device(id, std::move(fd), std::move(callback)));
 }
 
-Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) :
-            mId(id), mFd(fd), mDeviceCallback(std::move(callback)) {
+Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback)
+      : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) {
     ALooper* aLooper = ALooper_forThread();
     if (aLooper == NULL) {
         LOGE("Could not get ALooper, ALooper_forThread returned NULL");
         aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
     }
-    ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
+    ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
                   reinterpret_cast<void*>(this));
 }
 
@@ -162,8 +194,14 @@
     struct uhid_event ev = {};
     ev.type = UHID_DESTROY;
     TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    ::close(mFd);
-    mFd = -1;
+}
+
+// Send event over the fd.
+static void writeEvent(int fd, struct uhid_event& ev, const char* messageType) {
+    ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
+    if (ret < 0 || ret != sizeof(ev)) {
+        LOGE("Failed to send uhid_event %s: %s", messageType, strerror(errno));
+    }
 }
 
 void Device::sendReport(const std::vector<uint8_t>& report) const {
@@ -176,10 +214,7 @@
     ev.type = UHID_INPUT2;
     ev.u.input2.size = report.size();
     memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0]));
-    ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    if (ret < 0 || ret != sizeof(ev)) {
-        LOGE("Failed to send hid event: %s", strerror(errno));
-    }
+    writeEvent(mFd, ev, "UHID_INPUT2");
 }
 
 void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const {
@@ -190,10 +225,7 @@
     ev.u.get_report_reply.size = report.size();
     memcpy(&ev.u.get_report_reply.data, report.data(),
             report.size() * sizeof(ev.u.get_report_reply.data[0]));
-    ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    if (ret < 0 || ret != sizeof(ev)) {
-        LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno));
-    }
+    writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY");
 }
 
 int Device::handleEvents(int events) {
@@ -210,13 +242,37 @@
         return 0;
     }
 
-    if (ev.type == UHID_OPEN) {
-        mDeviceCallback->onDeviceOpen();
-    } else if (ev.type == UHID_GET_REPORT) {
-        mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
-    } else if (ev.type == UHID_SET_REPORT) {
-        LOGE("UHID_SET_REPORT is currently not supported");
-        return 0;
+    switch (ev.type) {
+        case UHID_OPEN: {
+            mDeviceCallback->onDeviceOpen();
+            break;
+        }
+        case UHID_GET_REPORT: {
+            mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
+            break;
+        }
+        case UHID_SET_REPORT: {
+            const struct uhid_set_report_req& set_report = ev.u.set_report;
+            if (set_report.size > UHID_DATA_MAX) {
+                LOGE("SET_REPORT contains too much data: size = %" PRIu16, set_report.size);
+                return 0;
+            }
+
+            std::vector<uint8_t> data(set_report.data, set_report.data + set_report.size);
+            LOGI("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
+                 set_report.rnum, toString(data).c_str());
+            break;
+        }
+        case UHID_OUTPUT: {
+            struct uhid_output_req& output = ev.u.output;
+            std::vector<uint8_t> data(output.data, output.data + output.size);
+            mDeviceCallback->onDeviceOutput(data);
+            break;
+        }
+        default: {
+            LOGI("Unhandled event type: %" PRIu32, ev.type);
+            break;
+        }
     }
 
     return 1;
@@ -239,8 +295,8 @@
     return data;
 }
 
-static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid,
-        jbyteArray rawDescriptor, jobject callback) {
+static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
+                        jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
     ScopedUtfChars name(env, rawName);
     if (name.c_str() == nullptr) {
         return 0;
@@ -250,9 +306,10 @@
 
     std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
 
-    uhid::Device* d = uhid::Device::open(
-            id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc, std::move(cb));
-    return reinterpret_cast<jlong>(d);
+    std::unique_ptr<uhid::Device> d =
+            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
+                               std::move(cb));
+    return reinterpret_cast<jlong>(d.release());
 }
 
 static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
@@ -284,14 +341,14 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    { "nativeOpenDevice",
-            "(Ljava/lang/String;III[B"
-            "Lcom/android/commands/hid/Device$DeviceCallback;)J",
-            reinterpret_cast<void*>(openDevice) },
-    { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) },
-    { "nativeSendGetFeatureReportReply", "(JI[B)V",
-            reinterpret_cast<void*>(sendGetFeatureReportReply) },
-    { "nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice) },
+        {"nativeOpenDevice",
+         "(Ljava/lang/String;IIII[B"
+         "Lcom/android/commands/hid/Device$DeviceCallback;)J",
+         reinterpret_cast<void*>(openDevice)},
+        {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
+        {"nativeSendGetFeatureReportReply", "(JI[B)V",
+         reinterpret_cast<void*>(sendGetFeatureReportReply)},
+        {"nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice)},
 };
 
 int register_com_android_commands_hid_Device(JNIEnv* env) {
@@ -304,6 +361,8 @@
             env->GetMethodID(clazz, "onDeviceOpen", "()V");
     uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
             env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
+    uhid::gDeviceCallbackClassInfo.onDeviceOutput =
+            env->GetMethodID(clazz, "onDeviceOutput", "([B)V");
     uhid::gDeviceCallbackClassInfo.onDeviceError =
             env->GetMethodID(clazz, "onDeviceError", "()V");
     if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 892c7cd..7202b45 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -19,6 +19,8 @@
 
 #include <jni.h>
 
+#include <android-base/unique_fd.h>
+
 namespace android {
 namespace uhid {
 
@@ -29,6 +31,7 @@
 
     void onDeviceOpen();
     void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
+    void onDeviceOutput(const std::vector<uint8_t>& data);
     void onDeviceError();
 
 private:
@@ -39,10 +42,10 @@
 
 class Device {
 public:
-    static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid,
-            std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback);
+    static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
+                                        uint16_t bus, const std::vector<uint8_t>& descriptor,
+                                        std::unique_ptr<DeviceCallback> callback);
 
-    Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback);
     ~Device();
 
     void sendReport(const std::vector<uint8_t>& report) const;
@@ -52,8 +55,9 @@
     int handleEvents(int events);
 
 private:
+    Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback);
     int32_t mId;
-    int mFd;
+    android::base::unique_fd mFd;
     std::unique_ptr<DeviceCallback> mDeviceCallback;
 };
 
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 616d411..dade415 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -20,13 +20,16 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.os.SomeArgs;
 
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
 public class Device {
     private static final String TAG = "HidDevice";
 
@@ -40,6 +43,7 @@
     private final DeviceHandler mHandler;
     // mFeatureReports is limited to 256 entries, because the report number is 8-bit
     private final SparseArray<byte[]> mFeatureReports;
+    private final Map<ByteBuffer, byte[]> mOutputs;
     private long mTimeToSend;
 
     private final Object mCond = new Object();
@@ -48,23 +52,25 @@
         System.loadLibrary("hidcommand_jni");
     }
 
-    private static native long nativeOpenDevice(String name, int id, int vid, int pid,
+    private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus,
             byte[] descriptor, DeviceCallback callback);
     private static native void nativeSendReport(long ptr, byte[] data);
     private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data);
     private static native void nativeCloseDevice(long ptr);
 
-    public Device(int id, String name, int vid, int pid, byte[] descriptor,
-            byte[] report, SparseArray<byte[]> featureReports) {
+    public Device(int id, String name, int vid, int pid, int bus, byte[] descriptor,
+            byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) {
         mId = id;
         mThread = new HandlerThread("HidDeviceHandler");
         mThread.start();
         mHandler = new DeviceHandler(mThread.getLooper());
         mFeatureReports = featureReports;
+        mOutputs = outputs;
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = id;
         args.argi2 = vid;
         args.argi3 = pid;
+        args.argi4 = bus;
         if (name != null) {
             args.arg1 = name;
         } else {
@@ -110,7 +116,7 @@
                 case MSG_OPEN_DEVICE:
                     SomeArgs args = (SomeArgs) msg.obj;
                     mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3,
-                            (byte[]) args.arg2, new DeviceCallback());
+                            args.argi4, (byte[]) args.arg2, new DeviceCallback());
                     pauseEvents();
                     break;
                 case MSG_SEND_REPORT:
@@ -160,6 +166,11 @@
         }
 
         public void onDeviceGetReport(int requestId, int reportId) {
+            if (mFeatureReports == null) {
+                Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId
+                        + ", but 'feature_reports' section is not found");
+                return;
+            }
             byte[] report = mFeatureReports.get(reportId);
 
             if (report == null) {
@@ -176,6 +187,29 @@
             mHandler.sendMessageAtTime(msg, mTimeToSend);
         }
 
+        // native callback
+        public void onDeviceOutput(byte[] data) {
+            if (mOutputs == null) {
+                Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
+                return;
+            }
+            byte[] response = mOutputs.get(ByteBuffer.wrap(data));
+            if (response == null) {
+                Log.i(TAG,
+                        "Requested response for output " + Arrays.toString(data) + " is not found");
+                return;
+            }
+
+            Message msg;
+            msg = mHandler.obtainMessage(MSG_SEND_REPORT, response);
+
+            // Message is set to asynchronous so it won't be blocked by synchronization
+            // barrier during UHID_OPEN. This is necessary for drivers that do
+            // UHID_OUTPUT requests during probe, and expect a response right away.
+            msg.setAsynchronous(true);
+            mHandler.sendMessageAtTime(msg, mTimeToSend);
+        }
+
         public void onDeviceError() {
             Log.e(TAG, "Device error occurred, closing /dev/uhid");
             Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 746e372..d4bf1d8 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -21,10 +21,13 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 public class Event {
     private static final String TAG = "HidEvent";
@@ -33,14 +36,31 @@
     public static final String COMMAND_DELAY = "delay";
     public static final String COMMAND_REPORT = "report";
 
+    // These constants come from "include/uapi/linux/input.h" in the kernel
+    enum Bus {
+        USB(0x03), BLUETOOTH(0x05);
+
+        Bus(int value) {
+            mValue = value;
+        }
+
+        int getValue() {
+            return mValue;
+        }
+
+        private int mValue;
+    }
+
     private int mId;
     private String mCommand;
     private String mName;
     private byte[] mDescriptor;
     private int mVid;
     private int mPid;
+    private Bus mBus;
     private byte[] mReport;
     private SparseArray<byte[]> mFeatureReports;
+    private Map<ByteBuffer, byte[]> mOutputs;
     private int mDuration;
 
     public int getId() {
@@ -67,6 +87,10 @@
         return mPid;
     }
 
+    public int getBus() {
+        return mBus.getValue();
+    }
+
     public byte[] getReport() {
         return mReport;
     }
@@ -75,6 +99,10 @@
         return mFeatureReports;
     }
 
+    public Map<ByteBuffer, byte[]> getOutputs() {
+        return mOutputs;
+    }
+
     public int getDuration() {
         return mDuration;
     }
@@ -86,8 +114,10 @@
             + ", descriptor=" + Arrays.toString(mDescriptor)
             + ", vid=" + mVid
             + ", pid=" + mPid
+            + ", bus=" + mBus
             + ", report=" + Arrays.toString(mReport)
             + ", feature_reports=" + mFeatureReports.toString()
+            + ", outputs=" + mOutputs.toString()
             + ", duration=" + mDuration
             + "}";
     }
@@ -123,6 +153,10 @@
             mEvent.mFeatureReports = reports;
         }
 
+        public void setOutputs(Map<ByteBuffer, byte[]> outputs) {
+            mEvent.mOutputs = outputs;
+        }
+
         public void setVid(int vid) {
             mEvent.mVid = vid;
         }
@@ -131,6 +165,10 @@
             mEvent.mPid = pid;
         }
 
+        public void setBus(Bus bus) {
+            mEvent.mBus = bus;
+        }
+
         public void setDuration(int duration) {
             mEvent.mDuration = duration;
         }
@@ -193,12 +231,18 @@
                             case "pid":
                                 eb.setPid(readInt());
                                 break;
+                            case "bus":
+                                eb.setBus(readBus());
+                                break;
                             case "report":
                                 eb.setReport(readData());
                                 break;
                             case "feature_reports":
                                 eb.setFeatureReports(readFeatureReports());
                                 break;
+                            case "outputs":
+                                eb.setOutputs(readOutputs());
+                                break;
                             case "duration":
                                 eb.setDuration(readInt());
                                 break;
@@ -248,9 +292,14 @@
             return Integer.decode(val);
         }
 
+        private Bus readBus() throws IOException {
+            String val = mReader.nextString();
+            return Bus.valueOf(val.toUpperCase());
+        }
+
         private SparseArray<byte[]> readFeatureReports()
                 throws IllegalStateException, IOException {
-            SparseArray<byte[]> featureReports = new SparseArray();
+            SparseArray<byte[]> featureReports = new SparseArray<>();
             try {
                 mReader.beginArray();
                 while (mReader.hasNext()) {
@@ -276,17 +325,60 @@
                         }
                     }
                     mReader.endObject();
-                    if (data != null)
+                    if (data != null) {
                         featureReports.put(id, data);
+                    }
                 }
                 mReader.endArray();
-            } catch (IllegalStateException|NumberFormatException e) {
+            } catch (IllegalStateException | NumberFormatException e) {
                 consumeRemainingElements();
                 mReader.endArray();
                 throw new IllegalStateException("Encountered malformed data.", e);
-            } finally {
-                return featureReports;
             }
+            return featureReports;
+        }
+
+        private Map<ByteBuffer, byte[]> readOutputs()
+                throws IllegalStateException, IOException {
+            Map<ByteBuffer, byte[]> outputs = new HashMap<>();
+
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    byte[] output = null;
+                    byte[] response = null;
+                    mReader.beginObject();
+                    while (mReader.hasNext()) {
+                        String name = mReader.nextName();
+                        switch (name) {
+                            case "description":
+                                // Description is only used to keep track of the output responses
+                                mReader.nextString();
+                                break;
+                            case "output":
+                                output = readData();
+                                break;
+                            case "response":
+                                response = readData();
+                                break;
+                            default:
+                                consumeRemainingElements();
+                                mReader.endObject();
+                                throw new IllegalStateException("Invalid key in outputs: " + name);
+                        }
+                    }
+                    mReader.endObject();
+                    if (output != null) {
+                        outputs.put(ByteBuffer.wrap(output), response);
+                    }
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return outputs;
         }
 
         private void consumeRemainingElements() throws IOException {
@@ -296,10 +388,6 @@
         }
     }
 
-    private static void error(String msg) {
-        error(msg, null);
-    }
-
     private static void error(String msg, Exception e) {
         System.out.println(msg);
         Log.e(TAG, msg);
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 54ac1b0..fac0ab2 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -16,22 +16,17 @@
 
 package com.android.commands.hid;
 
-import android.util.JsonReader;
-import android.util.JsonToken;
 import android.util.Log;
 import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
 
 public class Hid {
     private static final String TAG = "HID";
@@ -118,8 +113,8 @@
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
         int id = e.getId();
-        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
-                e.getDescriptor(), e.getReport(), e.getFeatureReports());
+        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
+                e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
         mDevices.append(id, d);
     }
 
diff --git a/cmds/idmap/Android.bp b/cmds/idmap/Android.bp
deleted file mode 100644
index ae5d74a..0000000
--- a/cmds/idmap/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2012 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_binary {
-    name: "idmap",
-
-    srcs: [
-        "idmap.cpp",
-        "create.cpp",
-        "scan.cpp",
-        "inspect.cpp",
-    ],
-
-    shared_libs: [
-        "liblog",
-        "libutils",
-        "libandroidfw",
-        "libcutils",
-    ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wunused",
-        "-Wunreachable-code",
-    ],
-}
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
deleted file mode 100644
index f415f8f..0000000
--- a/cmds/idmap/create.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-#include "idmap.h"
-
-#include <memory>
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <androidfw/ZipFileRO.h>
-#include <utils/String8.h>
-
-#include <fcntl.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-
-using namespace android;
-
-namespace {
-    int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc)
-    {
-        std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(zip_path));
-        if (zip.get() == NULL) {
-            return -1;
-        }
-        ZipEntryRO entry = zip->findEntryByName(entry_name);
-        if (entry == NULL) {
-            return -1;
-        }
-        if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, crc)) {
-            return -1;
-        }
-        zip->releaseEntry(entry);
-        return 0;
-    }
-
-    int open_idmap(const char *path)
-    {
-        int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644));
-        if (fd == -1) {
-            ALOGD("error: open %s: %s\n", path, strerror(errno));
-            goto fail;
-        }
-        if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
-            ALOGD("error: fchmod %s: %s\n", path, strerror(errno));
-            goto fail;
-        }
-        if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX)) != 0) {
-            ALOGD("error: flock %s: %s\n", path, strerror(errno));
-            goto fail;
-        }
-
-        return fd;
-fail:
-        if (fd != -1) {
-            close(fd);
-            unlink(path);
-        }
-        return -1;
-    }
-
-    int write_idmap(int fd, const uint32_t *data, size_t size)
-    {
-        if (lseek(fd, 0, SEEK_SET) < 0) {
-            return -1;
-        }
-        size_t bytesLeft = size;
-        while (bytesLeft > 0) {
-            ssize_t w = TEMP_FAILURE_RETRY(write(fd, data + size - bytesLeft, bytesLeft));
-            if (w < 0) {
-                fprintf(stderr, "error: write: %s\n", strerror(errno));
-                return -1;
-            }
-            bytesLeft -= static_cast<size_t>(w);
-        }
-        return 0;
-    }
-
-    bool is_idmap_stale_fd(const char *target_apk_path, const char *overlay_apk_path, int idmap_fd)
-    {
-        static const size_t N = ResTable::IDMAP_HEADER_SIZE_BYTES;
-        struct stat st;
-        if (fstat(idmap_fd, &st) == -1) {
-            return true;
-        }
-        if (st.st_size < static_cast<off_t>(N)) {
-            // file is empty or corrupt
-            return true;
-        }
-
-        char buf[N];
-        size_t bytesLeft = N;
-        if (lseek(idmap_fd, 0, SEEK_SET) < 0) {
-            return true;
-        }
-        for (;;) {
-            ssize_t r = TEMP_FAILURE_RETRY(read(idmap_fd, buf + N - bytesLeft, bytesLeft));
-            if (r < 0) {
-                return true;
-            }
-            bytesLeft -= static_cast<size_t>(r);
-            if (bytesLeft == 0) {
-                break;
-            }
-            if (r == 0) {
-                // "shouldn't happen"
-                return true;
-            }
-        }
-
-        uint32_t version, cached_target_crc, cached_overlay_crc;
-        String8 cached_target_path, cached_overlay_path;
-        if (!ResTable::getIdmapInfo(buf, N, &version, &cached_target_crc, &cached_overlay_crc,
-                    &cached_target_path, &cached_overlay_path)) {
-            return true;
-        }
-
-        if (version != ResTable::IDMAP_CURRENT_VERSION) {
-            return true;
-        }
-
-        if (cached_target_path != target_apk_path) {
-            return true;
-        }
-        if (cached_overlay_path != overlay_apk_path) {
-            return true;
-        }
-
-        uint32_t actual_target_crc, actual_overlay_crc;
-        if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
-				&actual_target_crc) == -1) {
-            return true;
-        }
-        if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
-				&actual_overlay_crc) == -1) {
-            return true;
-        }
-
-        return cached_target_crc != actual_target_crc || cached_overlay_crc != actual_overlay_crc;
-    }
-
-    bool is_idmap_stale_path(const char *target_apk_path, const char *overlay_apk_path,
-            const char *idmap_path)
-    {
-        struct stat st;
-        if (stat(idmap_path, &st) == -1) {
-            // non-existing idmap is always stale; on other errors, abort idmap generation
-            return errno == ENOENT;
-        }
-
-        int idmap_fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY));
-        if (idmap_fd == -1) {
-            return false;
-        }
-        bool is_stale = is_idmap_stale_fd(target_apk_path, overlay_apk_path, idmap_fd);
-        close(idmap_fd);
-        return is_stale;
-    }
-
-    int create_idmap(const char *target_apk_path, const char *overlay_apk_path,
-            uint32_t **data, size_t *size)
-    {
-        uint32_t target_crc, overlay_crc;
-        if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
-				&target_crc) == -1) {
-            return -1;
-        }
-        if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
-				&overlay_crc) == -1) {
-            return -1;
-        }
-
-        AssetManager am;
-        bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc,
-                data, size);
-        return b ? 0 : -1;
-    }
-
-    int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path,
-            int fd, bool check_if_stale)
-    {
-        if (check_if_stale) {
-            if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) {
-                // already up to date -- nothing to do
-                return 0;
-            }
-        }
-
-        uint32_t *data = NULL;
-        size_t size;
-
-        if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) {
-            return -1;
-        }
-
-        if (write_idmap(fd, data, size) == -1) {
-            free(data);
-            return -1;
-        }
-
-        free(data);
-        return 0;
-    }
-}
-
-int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
-        const char *idmap_path)
-{
-    if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) {
-        // already up to date -- nothing to do
-        return EXIT_SUCCESS;
-    }
-
-    int fd = open_idmap(idmap_path);
-    if (fd == -1) {
-        return EXIT_FAILURE;
-    }
-
-    int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false);
-    close(fd);
-    if (r != 0) {
-        unlink(idmap_path);
-    }
-    return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
-}
-
-int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd)
-{
-    return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ?
-        EXIT_SUCCESS : EXIT_FAILURE;
-}
-
-int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd)
-{
-    return !is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd) ?
-            EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
deleted file mode 100644
index 8f86ed8..0000000
--- a/cmds/idmap/idmap.cpp
+++ /dev/null
@@ -1,283 +0,0 @@
-#include "idmap.h"
-
-#include <private/android_filesystem_config.h> // for AID_SYSTEM
-
-#include <stdlib.h>
-#include <string.h>
-
-namespace {
-    const char *usage = "NAME\n\
-      idmap - create or display idmap files\n\
-\n\
-SYNOPSIS \n\
-      idmap --help \n\
-      idmap --fd target overlay fd \n\
-      idmap --path target overlay idmap \n\
-      idmap --scan target-package-name-to-look-for path-to-target-apk dir-to-hold-idmaps \\\
-                   dir-to-scan [additional-dir-to-scan [additional-dir-to-scan [...]]]\n\
-      idmap --inspect idmap \n\
-      idmap --verify target overlay fd \n\
-\n\
-DESCRIPTION \n\
-      Idmap files play an integral part in the runtime resource overlay framework. An idmap \n\
-      file contains a mapping of resource identifiers between overlay package and its target \n\
-      package; this mapping is used during resource lookup. Idmap files also act as control \n\
-      files by their existence: if not present, the corresponding overlay package is ignored \n\
-      when the resource context is created. \n\
-\n\
-      Idmap files are stored in /data/resource-cache. For each pair (target package, overlay \n\
-      package), there exists exactly one idmap file, or none if the overlay should not be used. \n\
-\n\
-NOMENCLATURE \n\
-      target: the original, non-overlay, package. Each target package may be associated with \n\
-              any number of overlay packages. \n\
-\n\
-      overlay: an overlay package. Each overlay package is associated with exactly one target \n\
-               package, specified in the overlay's manifest using the <overlay target=\"...\"/> \n\
-               tag. \n\
-\n\
-OPTIONS \n\
-      --help: display this help \n\
-\n\
-      --fd: create idmap for target package 'target' (path to apk) and overlay package 'overlay' \n\
-            (path to apk); write results to file descriptor 'fd' (integer). This invocation \n\
-            version is intended to be used by a parent process with higher privileges to call \n\
-            idmap in a controlled way: the parent will open a suitable file descriptor, fork, \n\
-            drop its privileges and exec. This tool will continue execution without the extra \n\
-            privileges, but still have write access to a file it could not have opened on its \n\
-            own. \n\
-\n\
-      --path: create idmap for target package 'target' (path to apk) and overlay package \n\
-              'overlay' (path to apk); write results to 'idmap' (path). \n\
-\n\
-      --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\
-              with target package 'target-package-name-to-look-for' (package name) present at\n\
-              'path-to-target-apk' (path to apk). For each overlay package found, create an\n\
-              idmap file in 'dir-to-hold-idmaps' (path). \n\
-\n\
-      --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\
-                 debug-friendly format. \n\
-\n\
-      --verify: verify if idmap corresponding to file descriptor 'fd' (integer) is made from \n\
-                target package 'target' (path to apk) and overlay package 'overlay'. \n\
-\n\
-EXAMPLES \n\
-      Create an idmap file: \n\
-\n\
-      $ adb shell idmap --path /system/app/target.apk \\ \n\
-                               /vendor/overlay/overlay.apk \\ \n\
-                               /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\
-\n\
-      Display an idmap file: \n\
-\n\
-      $ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\
-      SECTION      ENTRY        VALUE      COMMENT \n\
-      IDMAP HEADER magic        0x706d6469 \n\
-                   base crc     0xb65a383f \n\
-                   overlay crc  0x7b9675e8 \n\
-                   base path    .......... /path/to/target.apk \n\
-                   overlay path .......... /path/to/overlay.apk \n\
-      DATA HEADER  target pkg   0x0000007f \n\
-                   types count  0x00000003 \n\
-      DATA BLOCK   target type  0x00000002 \n\
-                   overlay type 0x00000002 \n\
-                   entry count  0x00000001 \n\
-                   entry offset 0x00000000 \n\
-                   entry        0x00000000 drawable/drawable \n\
-      DATA BLOCK   target type  0x00000003 \n\
-                   overlay type 0x00000003 \n\
-                   entry count  0x00000001 \n\
-                   entry offset 0x00000000 \n\
-                   entry        0x00000000 xml/integer \n\
-      DATA BLOCK   target type  0x00000004 \n\
-                   overlay type 0x00000004 \n\
-                   entry count  0x00000001 \n\
-                   entry offset 0x00000000 \n\
-                   entry        0x00000000 raw/lorem_ipsum \n\
-\n\
-      In this example, the overlay package provides three alternative resource values:\n\
-      drawable/drawable, xml/integer, and raw/lorem_ipsum \n\
-\n\
-NOTES \n\
-      This tool and its expected invocation from installd is modelled on dexopt.";
-
-    bool verify_directory_readable(const char *path)
-    {
-        return access(path, R_OK | X_OK) == 0;
-    }
-
-    bool verify_directory_writable(const char *path)
-    {
-        return access(path, W_OK) == 0;
-    }
-
-    bool verify_file_readable(const char *path)
-    {
-        return access(path, R_OK) == 0;
-    }
-
-    bool verify_root_or_system()
-    {
-        uid_t uid = getuid();
-        gid_t gid = getgid();
-
-        return (uid == 0 && gid == 0) || (uid == AID_SYSTEM && gid == AID_SYSTEM);
-    }
-
-    int maybe_create_fd(const char *target_apk_path, const char *overlay_apk_path,
-            const char *idmap_str)
-    {
-        // anyone (not just root or system) may do --fd -- the file has
-        // already been opened by someone else on our behalf
-
-        char *endptr;
-        int idmap_fd = strtol(idmap_str, &endptr, 10);
-        if (*endptr != '\0') {
-            fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str);
-            return -1;
-        }
-
-        if (!verify_file_readable(target_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
-            return -1;
-        }
-
-        if (!verify_file_readable(overlay_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
-            return -1;
-        }
-
-        return idmap_create_fd(target_apk_path, overlay_apk_path, idmap_fd);
-    }
-
-    int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path,
-            const char *idmap_path)
-    {
-        if (!verify_root_or_system()) {
-            fprintf(stderr, "error: permission denied: not user root or user system\n");
-            return -1;
-        }
-
-        if (!verify_file_readable(target_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
-            return -1;
-        }
-
-        if (!verify_file_readable(overlay_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
-            return -1;
-        }
-
-        return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
-    }
-
-    int maybe_verify_fd(const char *target_apk_path, const char *overlay_apk_path,
-            const char *idmap_str)
-    {
-        char *endptr;
-        int idmap_fd = strtol(idmap_str, &endptr, 10);
-        if (*endptr != '\0') {
-            fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str);
-            return -1;
-        }
-
-        if (!verify_file_readable(target_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
-            return -1;
-        }
-
-        if (!verify_file_readable(overlay_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
-            return -1;
-        }
-
-        return idmap_verify_fd(target_apk_path, overlay_apk_path, idmap_fd);
-    }
-
-    int maybe_scan(const char *target_package_name, const char *target_apk_path,
-            const char *idmap_dir, const android::Vector<const char *> *overlay_dirs)
-    {
-        if (!verify_root_or_system()) {
-            fprintf(stderr, "error: permission denied: not user root or user system\n");
-            return -1;
-        }
-
-        if (!verify_file_readable(target_apk_path)) {
-            ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
-            return -1;
-        }
-
-        if (!verify_directory_writable(idmap_dir)) {
-            ALOGD("error: no write access to %s: %s\n", idmap_dir, strerror(errno));
-            return -1;
-        }
-
-        const size_t N = overlay_dirs->size();
-        for (size_t i = 0; i < N; i++) {
-            const char *dir = overlay_dirs->itemAt(i);
-            if (!verify_directory_readable(dir)) {
-                ALOGD("error: no read access to %s: %s\n", dir, strerror(errno));
-                return -1;
-            }
-        }
-
-        return idmap_scan(target_package_name, target_apk_path, idmap_dir, overlay_dirs);
-    }
-
-    int maybe_inspect(const char *idmap_path)
-    {
-        // anyone (not just root or system) may do --inspect
-        if (!verify_file_readable(idmap_path)) {
-            ALOGD("error: failed to read idmap %s: %s\n", idmap_path, strerror(errno));
-            return -1;
-        }
-        return idmap_inspect(idmap_path);
-    }
-}
-
-int main(int argc, char **argv)
-{
-#if 0
-    {
-        char buf[1024];
-        buf[0] = '\0';
-        for (int i = 0; i < argc; ++i) {
-            strncat(buf, argv[i], sizeof(buf) - 1);
-            strncat(buf, " ", sizeof(buf) - 1);
-        }
-        ALOGD("%s:%d: uid=%d gid=%d argv=%s\n", __FILE__, __LINE__, getuid(), getgid(), buf);
-    }
-#endif
-
-    if (argc == 2 && !strcmp(argv[1], "--help")) {
-        printf("%s\n", usage);
-        return 0;
-    }
-
-    if (argc == 5 && !strcmp(argv[1], "--fd")) {
-        return maybe_create_fd(argv[2], argv[3], argv[4]);
-    }
-
-    if (argc == 5 && !strcmp(argv[1], "--path")) {
-        return maybe_create_path(argv[2], argv[3], argv[4]);
-    }
-
-    if (argc == 5 && !strcmp(argv[1], "--verify")) {
-        return maybe_verify_fd(argv[2], argv[3], argv[4]);
-    }
-
-    if (argc >= 6 && !strcmp(argv[1], "--scan")) {
-        android::Vector<const char *> v;
-        for (int i = 5; i < argc; i++) {
-            v.push(argv[i]);
-        }
-        return maybe_scan(argv[2], argv[3], argv[4], &v);
-    }
-
-    if (argc == 3 && !strcmp(argv[1], "--inspect")) {
-        return maybe_inspect(argv[2]);
-    }
-
-    fprintf(stderr, "Usage: don't use this (cf dexopt usage).\n");
-    return EXIT_FAILURE;
-}
diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h
deleted file mode 100644
index 5962108..0000000
--- a/cmds/idmap/idmap.h
+++ /dev/null
@@ -1,38 +0,0 @@
-
-#ifndef _IDMAP_H_
-#define _IDMAP_H_
-
-#define LOG_TAG "idmap"
-
-#include <utils/Log.h>
-#include <utils/Vector.h>
-
-#include <errno.h>
-#include <stdio.h>
-
-#ifndef TEMP_FAILURE_RETRY
-// Used to retry syscalls that can return EINTR.
-#define TEMP_FAILURE_RETRY(exp) ({         \
-    typeof (exp) _rc;                      \
-    do {                                   \
-        _rc = (exp);                       \
-    } while (_rc == -1 && errno == EINTR); \
-    _rc; })
-#endif
-
-int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
-        const char *idmap_path);
-
-int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd);
-
-int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd);
-
-// Regarding target_package_name: the idmap_scan implementation should
-// be able to extract this from the manifest in target_apk_path,
-// simplifying the external API.
-int idmap_scan(const char *target_package_name, const char *target_apk_path,
-        const char *idmap_dir, const android::Vector<const char *> *overlay_dirs);
-
-int idmap_inspect(const char *idmap_path);
-
-#endif // _IDMAP_H_
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
deleted file mode 100644
index 20005e2..0000000
--- a/cmds/idmap/inspect.cpp
+++ /dev/null
@@ -1,313 +0,0 @@
-#include "idmap.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <utils/ByteOrder.h>
-#include <utils/String8.h>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-
-using namespace android;
-
-namespace {
-    static const uint32_t IDMAP_MAGIC = 0x504D4449;
-    static const size_t PATH_LENGTH = 256;
-
-    void printe(const char *fmt, ...);
-
-    class IdmapBuffer {
-        private:
-            const char* buf_;
-            size_t len_;
-            size_t pos_;
-        public:
-            IdmapBuffer() : buf_((const char *)MAP_FAILED), len_(0), pos_(0) {}
-
-            ~IdmapBuffer() {
-                if (buf_ != MAP_FAILED) {
-                    munmap(const_cast<char*>(buf_), len_);
-                }
-            }
-
-            status_t init(const char *idmap_path) {
-                struct stat st;
-                int fd;
-
-                if (stat(idmap_path, &st) < 0) {
-                    printe("failed to stat idmap '%s': %s\n", idmap_path, strerror(errno));
-                    return UNKNOWN_ERROR;
-                }
-                len_ = st.st_size;
-                if ((fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY))) < 0) {
-                    printe("failed to open idmap '%s': %s\n", idmap_path, strerror(errno));
-                    return UNKNOWN_ERROR;
-                }
-                if ((buf_ = (const char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
-                    close(fd);
-                    printe("failed to mmap idmap: %s\n", strerror(errno));
-                    return UNKNOWN_ERROR;
-                }
-                close(fd);
-                return NO_ERROR;
-            }
-
-            status_t nextUint32(uint32_t* i) {
-                if (!buf_) {
-                    printe("failed to read next uint32_t: buffer not initialized\n");
-                    return UNKNOWN_ERROR;
-                }
-
-                if (pos_ + sizeof(uint32_t) > len_) {
-                    printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n",
-                            pos_);
-                    return UNKNOWN_ERROR;
-                }
-
-                if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x3) != 0) {
-                    printe("failed to read next uint32_t: not aligned on 4-byte boundary\n");
-                    return UNKNOWN_ERROR;
-                }
-
-                *i = dtohl(*reinterpret_cast<const uint32_t*>(buf_ + pos_));
-                pos_ += sizeof(uint32_t);
-                return NO_ERROR;
-            }
-
-            status_t nextUint16(uint16_t* i) {
-                if (!buf_) {
-                    printe("failed to read next uint16_t: buffer not initialized\n");
-                    return UNKNOWN_ERROR;
-                }
-
-                if (pos_ + sizeof(uint16_t) > len_) {
-                    printe("failed to read next uint16_t: end of buffer reached at pos=0x%08x\n",
-                            pos_);
-                    return UNKNOWN_ERROR;
-                }
-
-                if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x1) != 0) {
-                    printe("failed to read next uint32_t: not aligned on 2-byte boundary\n");
-                    return UNKNOWN_ERROR;
-                }
-
-                *i = dtohs(*reinterpret_cast<const uint16_t*>(buf_ + pos_));
-                pos_ += sizeof(uint16_t);
-                return NO_ERROR;
-            }
-
-            status_t nextPath(char *b) {
-                if (!buf_) {
-                    printe("failed to read next path: buffer not initialized\n");
-                    return UNKNOWN_ERROR;
-                }
-                if (pos_ + PATH_LENGTH > len_) {
-                    printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_);
-                    return UNKNOWN_ERROR;
-                }
-                memcpy(b, buf_ + pos_, PATH_LENGTH);
-                pos_ += PATH_LENGTH;
-                return NO_ERROR;
-            }
-    };
-
-    void printe(const char *fmt, ...) {
-        va_list ap;
-
-        va_start(ap, fmt);
-        fprintf(stderr, "error: ");
-        vfprintf(stderr, fmt, ap);
-        va_end(ap);
-    }
-
-    void print_header() {
-        printf("SECTION      ENTRY        VALUE      COMMENT\n");
-    }
-
-    void print(const char *section, const char *subsection, uint32_t value, const char *fmt, ...) {
-        va_list ap;
-
-        va_start(ap, fmt);
-        printf("%-12s %-12s 0x%08x ", section, subsection, value);
-        vprintf(fmt, ap);
-        printf("\n");
-        va_end(ap);
-    }
-
-    void print_path(const char *section, const char *subsection, const char *fmt, ...) {
-        va_list ap;
-
-        va_start(ap, fmt);
-        printf("%-12s %-12s .......... ", section, subsection);
-        vprintf(fmt, ap);
-        printf("\n");
-        va_end(ap);
-    }
-
-    status_t resource_metadata(const AssetManager& am, uint32_t res_id,
-            String8 *package, String8 *type, String8 *name) {
-        const ResTable& rt = am.getResources();
-        struct ResTable::resource_name data;
-        if (!rt.getResourceName(res_id, false, &data)) {
-            printe("failed to get resource name id=0x%08x\n", res_id);
-            return UNKNOWN_ERROR;
-        }
-        if (package != NULL) {
-            *package = String8(String16(data.package, data.packageLen));
-        }
-        if (type != NULL) {
-            *type = String8(String16(data.type, data.typeLen));
-        }
-        if (name != NULL) {
-            *name = String8(String16(data.name, data.nameLen));
-        }
-        return NO_ERROR;
-    }
-
-    status_t parse_idmap_header(IdmapBuffer& buf, AssetManager& am) {
-        uint32_t i;
-        char path[PATH_LENGTH];
-
-        status_t err = buf.nextUint32(&i);
-        if (err != NO_ERROR) {
-            return err;
-        }
-
-        if (i != IDMAP_MAGIC) {
-            printe("not an idmap file: actual magic constant 0x%08x does not match expected magic "
-                    "constant 0x%08x\n", i, IDMAP_MAGIC);
-            return UNKNOWN_ERROR;
-        }
-
-        print_header();
-        print("IDMAP HEADER", "magic", i, "");
-
-        err = buf.nextUint32(&i);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        print("", "version", i, "");
-
-        err = buf.nextUint32(&i);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        print("", "base crc", i, "");
-
-        err = buf.nextUint32(&i);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        print("", "overlay crc", i, "");
-
-        err = buf.nextPath(path);
-        if (err != NO_ERROR) {
-            // printe done from IdmapBuffer::nextPath
-            return err;
-        }
-        print_path("", "base path", "%s", path);
-
-        if (!am.addAssetPath(String8(path), NULL)) {
-            printe("failed to add '%s' as asset path\n", path);
-            return UNKNOWN_ERROR;
-        }
-
-        err = buf.nextPath(path);
-        if (err != NO_ERROR) {
-            // printe done from IdmapBuffer::nextPath
-            return err;
-        }
-        print_path("", "overlay path", "%s", path);
-
-        return NO_ERROR;
-    }
-
-    status_t parse_data(IdmapBuffer& buf, const AssetManager& am) {
-        const uint32_t packageId = am.getResources().getBasePackageId(0);
-
-        uint16_t data16;
-        status_t err = buf.nextUint16(&data16);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        print("DATA HEADER", "target pkg", static_cast<uint32_t>(data16), "");
-
-        err = buf.nextUint16(&data16);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        print("", "types count", static_cast<uint32_t>(data16), "");
-
-        uint32_t typeCount = static_cast<uint32_t>(data16);
-        while (typeCount > 0) {
-            typeCount--;
-
-            err = buf.nextUint16(&data16);
-            if (err != NO_ERROR) {
-                return err;
-            }
-            const uint32_t targetTypeId = static_cast<uint32_t>(data16);
-            print("DATA BLOCK", "target type", targetTypeId, "");
-
-            err = buf.nextUint16(&data16);
-            if (err != NO_ERROR) {
-                return err;
-            }
-            print("", "overlay type", static_cast<uint32_t>(data16), "");
-
-            err = buf.nextUint16(&data16);
-            if (err != NO_ERROR) {
-                return err;
-            }
-            const uint32_t entryCount = static_cast<uint32_t>(data16);
-            print("", "entry count", entryCount, "");
-
-            err = buf.nextUint16(&data16);
-            if (err != NO_ERROR) {
-                return err;
-            }
-            const uint32_t entryOffset = static_cast<uint32_t>(data16);
-            print("", "entry offset", entryOffset, "");
-
-            for (uint32_t i = 0; i < entryCount; i++) {
-                uint32_t data32;
-                err = buf.nextUint32(&data32);
-                if (err != NO_ERROR) {
-                    return err;
-                }
-
-                uint32_t resID = (packageId << 24) | (targetTypeId << 16) | (entryOffset + i);
-                String8 type;
-                String8 name;
-                err = resource_metadata(am, resID, NULL, &type, &name);
-                if (err != NO_ERROR) {
-                    return err;
-                }
-                if (data32 != ResTable_type::NO_ENTRY) {
-                    print("", "entry", data32, "%s/%s", type.string(), name.string());
-                }
-            }
-        }
-
-        return NO_ERROR;
-    }
-}
-
-int idmap_inspect(const char *idmap_path) {
-    IdmapBuffer buf;
-    if (buf.init(idmap_path) < 0) {
-        // printe done from IdmapBuffer::init
-        return EXIT_FAILURE;
-    }
-    AssetManager am;
-    if (parse_idmap_header(buf, am) != NO_ERROR) {
-        // printe done from parse_idmap_header
-        return EXIT_FAILURE;
-    }
-    if (parse_data(buf, am) != NO_ERROR) {
-        // printe done from parse_data_header
-        return EXIT_FAILURE;
-    }
-    return EXIT_SUCCESS;
-}
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
deleted file mode 100644
index 847dda3..0000000
--- a/cmds/idmap/scan.cpp
+++ /dev/null
@@ -1,281 +0,0 @@
-#include <dirent.h>
-#include <inttypes.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-
-#include "idmap.h"
-
-#include <memory>
-#include <androidfw/ResourceTypes.h>
-#include <androidfw/StreamingZipInflater.h>
-#include <androidfw/ZipFileRO.h>
-#include <cutils/properties.h>
-#include <private/android_filesystem_config.h> // for AID_SYSTEM
-#include <utils/SortedVector.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-
-#define NO_OVERLAY_TAG (-1000)
-
-using namespace android;
-
-namespace {
-    struct Overlay {
-        Overlay() {}
-        Overlay(const String8& a, const String8& i, int p) :
-            apk_path(a), idmap_path(i), priority(p) {}
-
-        bool operator<(Overlay const& rhs) const
-        {
-            return rhs.priority > priority;
-        }
-
-        String8 apk_path;
-        String8 idmap_path;
-        int priority;
-    };
-
-    bool writePackagesList(const char *filename, const SortedVector<Overlay>& overlayVector)
-    {
-        // the file is opened for appending so that it doesn't get truncated
-        // before we can guarantee mutual exclusion via the flock
-        FILE* fout = fopen(filename, "a");
-        if (fout == NULL) {
-            return false;
-        }
-
-        if (TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_EX)) != 0) {
-            fclose(fout);
-            return false;
-        }
-
-        if (TEMP_FAILURE_RETRY(ftruncate(fileno(fout), 0)) != 0) {
-            TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN));
-            fclose(fout);
-            return false;
-        }
-
-        for (size_t i = 0; i < overlayVector.size(); ++i) {
-            const Overlay& overlay = overlayVector[i];
-            fprintf(fout, "%s %s\n", overlay.apk_path.string(), overlay.idmap_path.string());
-        }
-
-        TEMP_FAILURE_RETRY(fflush(fout));
-        TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN));
-        fclose(fout);
-
-        // Make file world readable since Zygote (running as root) will read
-        // it when creating the initial AssetManger object
-        const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // 0644
-        if (chmod(filename, mode) == -1) {
-            unlink(filename);
-            return false;
-        }
-
-        return true;
-    }
-
-    String8 flatten_path(const char *path)
-    {
-        String16 tmp(path);
-        tmp.replaceAll('/', '@');
-        return String8(tmp);
-    }
-
-    bool check_property(String16 property, String16 value) {
-        char propBuf[PROPERTY_VALUE_MAX];
-        property_get(String8(property).c_str(), propBuf, NULL);
-        return String8(value) == propBuf;
-    }
-
-    int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name,
-            bool* is_static_overlay)
-    {
-        const size_t N = parser.getAttributeCount();
-        String16 target;
-        int priority = -1;
-        String16 propName = String16();
-        String16 propValue = String16();
-        for (size_t i = 0; i < N; ++i) {
-            size_t len;
-            String16 key(parser.getAttributeName(i, &len));
-            if (key == String16("targetPackage")) {
-                const char16_t *p = parser.getAttributeStringValue(i, &len);
-                if (p != NULL) {
-                    target = String16(p, len);
-                }
-            } else if (key == String16("priority")) {
-                Res_value v;
-                if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) {
-                    priority = v.data;
-                    if (priority < 0 || priority > 9999) {
-                        return -1;
-                    }
-                }
-            } else if (key == String16("isStatic")) {
-                Res_value v;
-                if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) {
-                    *is_static_overlay = (v.data != 0);
-                }
-            } else if (key == String16("requiredSystemPropertyName")) {
-                const char16_t *p = parser.getAttributeStringValue(i, &len);
-                if (p != NULL) {
-                    propName = String16(p, len);
-                }
-            } else if (key == String16("requiredSystemPropertyValue")) {
-                const char16_t *p = parser.getAttributeStringValue(i, &len);
-                if (p != NULL) {
-                    propValue = String16(p, len);
-                }
-            }
-        }
-
-        // Note that conditional property enablement/exclusion only applies if
-        // the attribute is present. In its absence, all overlays are presumed enabled.
-        if (propName.size() > 0 && propValue.size() > 0) {
-            // if property set & equal to value, then include overlay - otherwise skip
-            if (!check_property(propName, propValue)) {
-                return NO_OVERLAY_TAG;
-            }
-        }
-
-        if (target == String16(target_package_name)) {
-            return priority;
-        }
-        return NO_OVERLAY_TAG;
-    }
-
-    int parse_manifest(const void *data, size_t size, const char *target_package_name)
-    {
-        ResXMLTree parser;
-        parser.setTo(data, size);
-        if (parser.getError() != NO_ERROR) {
-            ALOGD("%s failed to init xml parser, error=0x%08x\n", __FUNCTION__, parser.getError());
-            return -1;
-        }
-
-        ResXMLParser::event_code_t type;
-        bool is_static_overlay = false;
-        int priority = NO_OVERLAY_TAG;
-        do {
-            type = parser.next();
-            if (type == ResXMLParser::START_TAG) {
-                size_t len;
-                String16 tag(parser.getElementName(&len));
-                if (tag == String16("overlay")) {
-                    priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay);
-                    break;
-                }
-            }
-        } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
-
-        if (is_static_overlay) {
-            return priority;
-        }
-        return NO_OVERLAY_TAG;
-    }
-
-    int parse_apk(const char *path, const char *target_package_name)
-    {
-        std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(path));
-        if (zip.get() == NULL) {
-            ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path);
-            return -1;
-        }
-        ZipEntryRO entry;
-        if ((entry = zip->findEntryByName("AndroidManifest.xml")) == NULL) {
-            ALOGW("%s: failed to find entry AndroidManifest.xml\n", __FUNCTION__);
-            return -1;
-        }
-        uint32_t uncompLen = 0;
-        uint16_t method;
-        if (!zip->getEntryInfo(entry, &method, &uncompLen, NULL, NULL, NULL, NULL)) {
-            ALOGW("%s: failed to read entry info\n", __FUNCTION__);
-            return -1;
-        }
-        if (method != ZipFileRO::kCompressDeflated) {
-            ALOGW("%s: cannot handle zip compression method %" PRIu16 "\n", __FUNCTION__, method);
-            return -1;
-        }
-        FileMap *dataMap = zip->createEntryFileMap(entry);
-        if (dataMap == NULL) {
-            ALOGW("%s: failed to create FileMap\n", __FUNCTION__);
-            return -1;
-        }
-        char *buf = new char[uncompLen];
-        if (NULL == buf) {
-            ALOGW("%s: failed to allocate %" PRIu32 " byte\n", __FUNCTION__, uncompLen);
-            delete dataMap;
-            return -1;
-        }
-        StreamingZipInflater inflater(dataMap, uncompLen);
-        if (inflater.read(buf, uncompLen) < 0) {
-            ALOGW("%s: failed to inflate %" PRIu32 " byte\n", __FUNCTION__, uncompLen);
-            delete[] buf;
-            delete dataMap;
-            return -1;
-        }
-
-        int priority = parse_manifest(buf, static_cast<size_t>(uncompLen), target_package_name);
-        delete[] buf;
-        delete dataMap;
-        return priority;
-    }
-}
-
-int idmap_scan(const char *target_package_name, const char *target_apk_path,
-        const char *idmap_dir, const android::Vector<const char *> *overlay_dirs)
-{
-    String8 filename = String8(idmap_dir);
-    filename.appendPath("overlays.list");
-
-    SortedVector<Overlay> overlayVector;
-    const size_t N = overlay_dirs->size();
-    for (size_t i = 0; i < N; ++i) {
-        const char *overlay_dir = overlay_dirs->itemAt(i);
-        DIR *dir = opendir(overlay_dir);
-        if (dir == NULL) {
-            return EXIT_FAILURE;
-        }
-
-        struct dirent *dirent;
-        while ((dirent = readdir(dir)) != NULL) {
-            struct stat st;
-            char overlay_apk_path[PATH_MAX + 1];
-            snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name);
-            if (stat(overlay_apk_path, &st) < 0) {
-                continue;
-            }
-            if (!S_ISREG(st.st_mode)) {
-                continue;
-            }
-
-            int priority = parse_apk(overlay_apk_path, target_package_name);
-            if (priority < 0) {
-                continue;
-            }
-
-            String8 idmap_path(idmap_dir);
-            idmap_path.appendPath(flatten_path(overlay_apk_path + 1));
-            idmap_path.append("@idmap");
-
-            if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) {
-                ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n",
-                        target_apk_path, overlay_apk_path, idmap_path.string());
-                continue;
-            }
-
-            Overlay overlay(String8(overlay_apk_path), idmap_path, priority);
-            overlayVector.add(overlay);
-        }
-
-        closedir(dir);
-    }
-
-    if (!writePackagesList(filename.string(), overlayVector)) {
-        return EXIT_FAILURE;
-    }
-
-    return EXIT_SUCCESS;
-}
-
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4e57e88..878cef9 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -43,17 +43,7 @@
     ],
     host_supported: true,
     srcs: [
-        "libidmap2/BinaryStreamVisitor.cpp",
-        "libidmap2/CommandLineOptions.cpp",
-        "libidmap2/FileUtils.cpp",
-        "libidmap2/Idmap.cpp",
-        "libidmap2/Policies.cpp",
-        "libidmap2/PrettyPrintVisitor.cpp",
-        "libidmap2/RawPrintVisitor.cpp",
-        "libidmap2/ResourceUtils.cpp",
-        "libidmap2/Result.cpp",
-        "libidmap2/Xml.cpp",
-        "libidmap2/ZipFile.cpp",
+        "libidmap2/**/*.cpp",
     ],
     export_include_dirs: ["include"],
     target: {
@@ -67,6 +57,7 @@
                 "libcutils",
                 "libutils",
                 "libziparchive",
+                "libidmap2_policies",
             ],
         },
         host: {
@@ -79,6 +70,37 @@
                 "libcutils",
                 "libutils",
                 "libziparchive",
+                "libidmap2_policies",
+            ],
+        },
+    },
+}
+
+cc_library {
+    name: "libidmap2_policies",
+    defaults: [
+        "idmap2_defaults",
+    ],
+    host_supported: true,
+    export_include_dirs: ["libidmap2_policies/include"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+        android: {
+            static: {
+                enabled: false,
+            },
+            shared_libs: [
+                "libandroidfw",
+            ],
+        },
+        host: {
+            shared: {
+                enabled: false,
+            },
+            static_libs: [
+                "libandroidfw",
             ],
         },
     },
@@ -104,9 +126,10 @@
         "tests/PoliciesTests.cpp",
         "tests/PrettyPrintVisitorTests.cpp",
         "tests/RawPrintVisitorTests.cpp",
+        "tests/ResourceMappingTests.cpp",
         "tests/ResourceUtilsTests.cpp",
         "tests/ResultTests.cpp",
-        "tests/XmlTests.cpp",
+        "tests/XmlParserTests.cpp",
         "tests/ZipFileTests.cpp",
     ],
     required: [
@@ -123,6 +146,7 @@
                 "libutils",
                 "libz",
                 "libziparchive",
+                "libidmap2_policies",
             ],
         },
         host: {
@@ -134,6 +158,7 @@
                 "liblog",
                 "libutils",
                 "libziparchive",
+                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
@@ -150,12 +175,13 @@
     ],
     host_supported: true,
     srcs: [
+        "idmap2/CommandUtils.cpp",
         "idmap2/Create.cpp",
+        "idmap2/CreateMultiple.cpp",
         "idmap2/Dump.cpp",
         "idmap2/Lookup.cpp",
         "idmap2/Main.cpp",
         "idmap2/Scan.cpp",
-        "idmap2/Verify.cpp",
     ],
     target: {
         android: {
@@ -166,6 +192,7 @@
                 "libidmap2",
                 "libutils",
                 "libziparchive",
+                "libidmap2_policies",
             ],
         },
         host: {
@@ -177,12 +204,14 @@
                 "liblog",
                 "libutils",
                 "libziparchive",
+                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
             ],
         },
     },
+
 }
 
 cc_binary {
@@ -203,6 +232,7 @@
         "libidmap2",
         "libutils",
         "libziparchive",
+        "libidmap2_policies",
     ],
     static_libs: [
         "libidmap2daidl",
@@ -235,3 +265,17 @@
     ],
     path: "idmap2d/aidl",
 }
+
+aidl_interface {
+    name: "overlayable_policy_aidl",
+    unstable: true,
+    srcs: [":overlayable_policy_aidl_files"],
+}
+
+filegroup {
+    name: "overlayable_policy_aidl_files",
+    srcs: [
+        "idmap2d/aidl/android/os/OverlayablePolicy.aidl",
+    ],
+    path: "idmap2d/aidl",
+}
diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg
index 9dc6b4a..20ed43c 100644
--- a/cmds/idmap2/CPPLINT.cfg
+++ b/cmds/idmap2/CPPLINT.cfg
@@ -15,4 +15,4 @@
 set noparent
 linelength=100
 root=..
-filter=+build/include_alpha
+filter=+build/include_alpha,-runtime/references,-build/c++
diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING
index 26ccf03..9e0fb84 100644
--- a/cmds/idmap2/TEST_MAPPING
+++ b/cmds/idmap2/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name" : "idmap2_tests"
     }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/core/java/com/android/server/om"
+    }
   ]
 }
diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
similarity index 70%
rename from cmds/idmap2/idmap2/Verify.cpp
rename to cmds/idmap2/idmap2/CommandUtils.cpp
index 9cb67b3..8f5845b 100644
--- a/cmds/idmap2/idmap2/Verify.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -19,30 +19,19 @@
 #include <string>
 #include <vector>
 
-#include "idmap2/CommandLineOptions.h"
 #include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
 
-using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
 using android::idmap2::Result;
 using android::idmap2::Unit;
 
-Result<Unit> Verify(const std::vector<std::string>& args) {
-  SYSTRACE << "Verify " << args;
-  std::string idmap_path;
-
-  const CommandLineOptions opts =
-      CommandLineOptions("idmap2 verify")
-          .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path);
-
-  const auto opts_ok = opts.Parse(args);
-  if (!opts_ok) {
-    return opts_ok.GetError();
-  }
-
+Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
+                    const std::string& overlay_path, PolicyBitmask fulfilled_policies,
+                    bool enforce_overlayable) {
+  SYSTRACE << "Verify " << idmap_path;
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
   fin.close();
@@ -50,7 +39,8 @@
     return Error("failed to parse idmap header");
   }
 
-  const auto header_ok = header->IsUpToDate();
+  const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(),
+                                            fulfilled_policies, enforce_overlayable);
   if (!header_ok) {
     return Error(header_ok.GetError(), "idmap not up to date");
   }
diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h
new file mode 100644
index 0000000..e717e04
--- /dev/null
+++ b/cmds/idmap2/idmap2/CommandUtils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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 IDMAP2_IDMAP2_COMMAND_UTILS_H_
+#define IDMAP2_IDMAP2_COMMAND_UTILS_H_
+
+#include "idmap2/PolicyUtils.h"
+#include "idmap2/Result.h"
+
+android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path,
+                                                      const std::string& target_path,
+                                                      const std::string& overlay_path,
+                                                      PolicyBitmask fulfilled_policies,
+                                                      bool enforce_overlayable);
+
+#endif  // IDMAP2_IDMAP2_COMMAND_UTILS_H_
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
index 718e361..69eea8d 100644
--- a/cmds/idmap2/idmap2/Commands.h
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -23,9 +23,9 @@
 #include "idmap2/Result.h"
 
 android::idmap2::Result<android::idmap2::Unit> Create(const std::vector<std::string>& args);
+android::idmap2::Result<android::idmap2::Unit> CreateMultiple(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Dump(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Lookup(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Scan(const std::vector<std::string>& args);
-android::idmap2::Result<android::idmap2::Unit> Verify(const std::vector<std::string>& args);
 
 #endif  // IDMAP2_IDMAP2_COMMANDS_H_
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index f482191..9682b6ea 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -20,15 +20,14 @@
 #include <fstream>
 #include <memory>
 #include <ostream>
-#include <sstream>
-#include <string>
 #include <vector>
 
+#include "androidfw/ResourceTypes.h"
 #include "idmap2/BinaryStreamVisitor.h"
 #include "idmap2/CommandLineOptions.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
-#include "idmap2/Policies.h"
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/SysTrace.h"
 
 using android::ApkAssets;
@@ -36,14 +35,15 @@
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
-using android::idmap2::PoliciesToBitmask;
-using android::idmap2::PolicyBitmask;
-using android::idmap2::PolicyFlags;
 using android::idmap2::Result;
 using android::idmap2::Unit;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::PoliciesToBitmaskResult;
 using android::idmap2::utils::UidHasWriteAccessToPath;
 
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
 Result<Unit> Create(const std::vector<std::string>& args) {
   SYSTRACE << "Create " << args;
   std::string target_apk_path;
@@ -78,7 +78,7 @@
   }
 
   PolicyBitmask fulfilled_policies = 0;
-  auto conv_result = PoliciesToBitmask(policies);
+  auto conv_result = PoliciesToBitmaskResult(policies);
   if (conv_result) {
     fulfilled_policies |= *conv_result;
   } else {
@@ -86,7 +86,7 @@
   }
 
   if (fulfilled_policies == 0) {
-    fulfilled_policies |= PolicyFlags::POLICY_PUBLIC;
+    fulfilled_policies |= PolicyFlags::PUBLIC;
   }
 
   const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
@@ -99,8 +99,8 @@
     return Error("failed to load apk %s", overlay_apk_path.c_str());
   }
 
-  const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
-                                          *overlay_apk, fulfilled_policies, !ignore_overlayable);
+  const auto idmap =
+      Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
   if (!idmap) {
     return Error(idmap.GetError(), "failed to create idmap");
   }
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
new file mode 100644
index 0000000..abdfaf4
--- /dev/null
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 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 <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <vector>
+
+#include "Commands.h"
+#include "android-base/stringprintf.h"
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/CommandUtils.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Policies.h"
+#include "idmap2/PolicyUtils.h"
+#include "idmap2/SysTrace.h"
+
+using android::ApkAssets;
+using android::base::StringPrintf;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Error;
+using android::idmap2::Idmap;
+using android::idmap2::Result;
+using android::idmap2::Unit;
+using android::idmap2::utils::kIdmapCacheDir;
+using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::PoliciesToBitmaskResult;
+using android::idmap2::utils::UidHasWriteAccessToPath;
+
+Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
+  SYSTRACE << "CreateMultiple " << args;
+  std::string target_apk_path;
+  std::string idmap_dir = kIdmapCacheDir;
+  std::vector<std::string> overlay_apk_paths;
+  std::vector<std::string> policies;
+  bool ignore_overlayable = false;
+
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 create-multiple")
+          .MandatoryOption("--target-apk-path",
+                           "input: path to apk which will have its resources overlaid",
+                           &target_apk_path)
+          .MandatoryOption("--overlay-apk-path",
+                           "input: path to apk which contains the new resource values",
+                           &overlay_apk_paths)
+          .OptionalOption("--idmap-dir",
+                          StringPrintf("output: path to the directory in which to write idmap file"
+                                       " (defaults to %s)",
+                                       kIdmapCacheDir),
+                          &idmap_dir)
+          .OptionalOption("--policy",
+                          "input: an overlayable policy this overlay fulfills"
+                          " (if none or supplied, the overlay policy will default to \"public\")",
+                          &policies)
+          .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks",
+                        &ignore_overlayable);
+  const auto opts_ok = opts.Parse(args);
+  if (!opts_ok) {
+    return opts_ok.GetError();
+  }
+
+  PolicyBitmask fulfilled_policies = 0;
+  auto conv_result = PoliciesToBitmaskResult(policies);
+  if (conv_result) {
+    fulfilled_policies |= *conv_result;
+  } else {
+    return conv_result.GetError();
+  }
+
+  if (fulfilled_policies == 0) {
+    fulfilled_policies |= PolicyFlags::PUBLIC;
+  }
+
+  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return Error("failed to load apk %s", target_apk_path.c_str());
+  }
+
+  std::vector<std::string> idmap_paths;
+  for (const std::string& overlay_apk_path : overlay_apk_paths) {
+    const std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path);
+    const uid_t uid = getuid();
+    if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+      LOG(WARNING) << "uid " << uid << "does not have write access to " << idmap_path.c_str();
+      continue;
+    }
+
+    if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies,
+                !ignore_overlayable)) {
+      const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+      if (!overlay_apk) {
+        LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
+        continue;
+      }
+
+      const auto idmap =
+          Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
+      if (!idmap) {
+        LOG(WARNING) << "failed to create idmap";
+        continue;
+      }
+
+      umask(kIdmapFilePermissionMask);
+      std::ofstream fout(idmap_path);
+      if (fout.fail()) {
+        LOG(WARNING) << "failed to open idmap path " << idmap_path.c_str();
+        continue;
+      }
+
+      BinaryStreamVisitor visitor(fout);
+      (*idmap)->accept(&visitor);
+      fout.close();
+      if (fout.fail()) {
+        LOG(WARNING) << "failed to write to idmap path %s" << idmap_path.c_str();
+        continue;
+      }
+    }
+
+    idmap_paths.emplace_back(idmap_path);
+  }
+
+  for (const std::string& idmap_path : idmap_paths) {
+    std::cout << idmap_path << std::endl;
+  }
+
+  return Unit{};
+}
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index b7ae9d0..c441709 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -33,9 +33,10 @@
 #include "androidfw/Util.h"
 #include "idmap2/CommandLineOptions.h"
 #include "idmap2/Idmap.h"
+#include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/Xml.h"
+#include "idmap2/XmlParser.h"
 #include "idmap2/ZipFile.h"
 #include "utils/String16.h"
 #include "utils/String8.h"
@@ -57,8 +58,7 @@
 using android::idmap2::ResourceId;
 using android::idmap2::Result;
 using android::idmap2::Unit;
-using android::idmap2::Xml;
-using android::idmap2::ZipFile;
+using android::idmap2::utils::ExtractOverlayManifestInfo;
 using android::util::Utf16ToUtf8;
 
 namespace {
@@ -85,11 +85,42 @@
   return Error("failed to obtain resource id for %s", res.c_str());
 }
 
-Result<std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) {
+void PrintValue(AssetManager2* const am, const Res_value& value, const ApkAssetsCookie& cookie,
+                std::string* const out) {
+  switch (value.dataType) {
+    case Res_value::TYPE_INT_DEC:
+      out->append(StringPrintf("%d", value.data));
+      break;
+    case Res_value::TYPE_INT_HEX:
+      out->append(StringPrintf("0x%08x", value.data));
+      break;
+    case Res_value::TYPE_INT_BOOLEAN:
+      out->append(value.data != 0 ? "true" : "false");
+      break;
+    case Res_value::TYPE_STRING: {
+      const ResStringPool* pool = am->GetStringPoolForCookie(cookie);
+      out->append("\"");
+      size_t len;
+      if (pool->isUTF8()) {
+        const char* str = pool->string8At(value.data, &len);
+        out->append(str, len);
+      } else {
+        const char16_t* str16 = pool->stringAt(value.data, &len);
+        out->append(Utf16ToUtf8(StringPiece16(str16, len)));
+      }
+      out->append("\"");
+    } break;
+    default:
+      out->append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data));
+      break;
+  }
+}
+
+Result<std::string> WARN_UNUSED GetValue(AssetManager2* const am, ResourceId resid) {
   Res_value value;
   ResTable_config config;
   uint32_t flags;
-  ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags);
+  ApkAssetsCookie cookie = am->GetResource(resid, true, 0, &value, &config, &flags);
   if (cookie == kInvalidCookie) {
     return Error("no resource 0x%08x in asset manager", resid);
   }
@@ -104,57 +135,40 @@
   out.append(config.toString().c_str());
   out.append("' value=");
 
-  switch (value.dataType) {
-    case Res_value::TYPE_INT_DEC:
-      out.append(StringPrintf("%d", value.data));
-      break;
-    case Res_value::TYPE_INT_HEX:
-      out.append(StringPrintf("0x%08x", value.data));
-      break;
-    case Res_value::TYPE_INT_BOOLEAN:
-      out.append(value.data != 0 ? "true" : "false");
-      break;
-    case Res_value::TYPE_STRING: {
-      const ResStringPool* pool = am.GetStringPoolForCookie(cookie);
-      size_t len;
-      if (pool->isUTF8()) {
-        const char* str = pool->string8At(value.data, &len);
-        out.append(str, len);
-      } else {
-        const char16_t* str16 = pool->stringAt(value.data, &len);
-        out += Utf16ToUtf8(StringPiece16(str16, len));
-      }
-    } break;
-    default:
+  if (value.dataType == Res_value::TYPE_REFERENCE) {
+    const android::ResolvedBag* bag = am->GetBag(static_cast<uint32_t>(value.data));
+    if (bag == nullptr) {
       out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data));
-      break;
+      return out;
+    }
+    out.append("[");
+    Res_value bag_val;
+    ResTable_config selected_config;
+    uint32_t flags;
+    uint32_t ref;
+    ApkAssetsCookie bag_cookie;
+    for (size_t i = 0; i < bag->entry_count; ++i) {
+      const android::ResolvedBag::Entry& entry = bag->entries[i];
+      bag_val = entry.value;
+      bag_cookie = am->ResolveReference(entry.cookie, &bag_val, &selected_config, &flags, &ref);
+      if (bag_cookie == kInvalidCookie) {
+        out.append(
+            StringPrintf("Error: dataType=0x%02x data=0x%08x", bag_val.dataType, bag_val.data));
+        continue;
+      }
+      PrintValue(am, bag_val, bag_cookie, &out);
+      if (i != bag->entry_count - 1) {
+        out.append(", ");
+      }
+    }
+    out.append("]");
+  } else {
+    PrintValue(am, value, cookie, &out);
   }
+
   return out;
 }
 
-Result<std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) {
-  const auto zip = ZipFile::Open(apk_path);
-  if (!zip) {
-    return Error("failed to open %s as zip", apk_path.c_str());
-  }
-  const auto entry = zip->Uncompress("AndroidManifest.xml");
-  if (!entry) {
-    return Error("failed to uncompress AndroidManifest.xml in %s", apk_path.c_str());
-  }
-  const auto xml = Xml::Create(entry->buf, entry->size);
-  if (!xml) {
-    return Error("failed to create XML buffer");
-  }
-  const auto tag = xml->FindTag("overlay");
-  if (!tag) {
-    return Error("failed to find <overlay> tag");
-  }
-  const auto iter = tag->find("targetPackage");
-  if (iter == tag->end()) {
-    return Error("failed to find targetPackage attribute");
-  }
-  return iter->second;
-}
 }  // namespace
 
 Result<Unit> Lookup(const std::vector<std::string>& args) {
@@ -202,12 +216,12 @@
       }
       apk_assets.push_back(std::move(target_apk));
 
-      const Result<std::string> package_name =
-          GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string());
-      if (!package_name) {
-        return Error("failed to parse android:targetPackage from overlay manifest");
+      auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath().to_string(),
+                                                      true /* assert_overlay */);
+      if (!manifest_info) {
+        return manifest_info.GetError();
       }
-      target_package_name = *package_name;
+      target_package_name = (*manifest_info).target_package;
     } else if (target_path != idmap_header->GetTargetPath()) {
       return Error("different target APKs (expected target APK %s but %s has target APK %s)",
                    target_path.c_str(), idmap_path.c_str(),
@@ -235,7 +249,7 @@
     return Error(resid.GetError(), "failed to parse resource ID");
   }
 
-  const Result<std::string> value = GetValue(am, *resid);
+  const Result<std::string> value = GetValue(&am, *resid);
   if (!value) {
     return Error(value.GetError(), "resource 0x%08x not found", *resid);
   }
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
index 8794908..fb093f0 100644
--- a/cmds/idmap2/idmap2/Main.cpp
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -53,7 +53,8 @@
 int main(int argc, char** argv) {
   SYSTRACE << "main";
   const NameToFunctionMap commands = {
-      {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify},
+      {"create", Create}, {"create-multiple", CreateMultiple}, {"dump", Dump}, {"lookup", Lookup},
+      {"scan", Scan},
   };
   if (argc <= 1) {
     PrintUsage(commands, std::cerr);
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index da8c06e..3625045 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -20,7 +20,6 @@
 #include <memory>
 #include <ostream>
 #include <set>
-#include <sstream>
 #include <string>
 #include <utility>
 #include <vector>
@@ -28,30 +27,33 @@
 #include "Commands.h"
 #include "android-base/properties.h"
 #include "idmap2/CommandLineOptions.h"
+#include "idmap2/CommandUtils.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
+#include "idmap2/Policies.h"
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/Xml.h"
-#include "idmap2/ZipFile.h"
+#include "idmap2/XmlParser.h"
 
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
-using android::idmap2::kPolicyOdm;
-using android::idmap2::kPolicyOem;
-using android::idmap2::kPolicyProduct;
-using android::idmap2::kPolicyPublic;
-using android::idmap2::kPolicySystem;
-using android::idmap2::kPolicyVendor;
-using android::idmap2::PolicyBitmask;
-using android::idmap2::PolicyFlags;
 using android::idmap2::Result;
 using android::idmap2::Unit;
+using android::idmap2::policy::kPolicyOdm;
+using android::idmap2::policy::kPolicyOem;
+using android::idmap2::policy::kPolicyProduct;
+using android::idmap2::policy::kPolicyPublic;
+using android::idmap2::policy::kPolicySystem;
+using android::idmap2::policy::kPolicyVendor;
 using android::idmap2::utils::ExtractOverlayManifestInfo;
 using android::idmap2::utils::FindFiles;
 using android::idmap2::utils::OverlayManifestInfo;
+using android::idmap2::utils::PoliciesToBitmaskResult;
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 
 namespace {
 
@@ -98,6 +100,7 @@
 }
 
 std::vector<std::string> PoliciesForPath(const std::string& apk_path) {
+  // clang-format off
   static const std::vector<std::pair<std::string, std::string>> values = {
       {"/odm/", kPolicyOdm},
       {"/oem/", kPolicyOem},
@@ -106,6 +109,7 @@
       {"/system_ext/", kPolicySystem},
       {"/vendor/", kPolicyVendor},
   };
+  // clang-format on
 
   std::vector<std::string> fulfilled_policies = {kPolicyPublic};
   for (auto const& pair : values) {
@@ -178,11 +182,11 @@
 
     // Note that conditional property enablement/exclusion only applies if
     // the attribute is present. In its absence, all overlays are presumed enabled.
-    if (!overlay_info->requiredSystemPropertyName.empty()
-        && !overlay_info->requiredSystemPropertyValue.empty()) {
+    if (!overlay_info->requiredSystemPropertyName.empty() &&
+        !overlay_info->requiredSystemPropertyValue.empty()) {
       // if property set & equal to value, then include overlay - otherwise skip
-      if (android::base::GetProperty(overlay_info->requiredSystemPropertyName, "")
-          != overlay_info->requiredSystemPropertyValue) {
+      if (android::base::GetProperty(overlay_info->requiredSystemPropertyName, "") !=
+          overlay_info->requiredSystemPropertyValue) {
         continue;
       }
     }
@@ -215,7 +219,15 @@
 
   std::stringstream stream;
   for (const auto& overlay : interesting_apks) {
-    if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}))) {
+    const auto policy_bitmask = PoliciesToBitmaskResult(overlay.policies);
+    if (!policy_bitmask) {
+      LOG(WARNING) << "failed to create idmap for overlay apk path \"" << overlay.apk_path
+                   << "\": " << policy_bitmask.GetErrorMessage();
+      continue;
+    }
+
+    if (!Verify(overlay.idmap_path, target_apk_path, overlay.apk_path, *policy_bitmask,
+                !overlay.ignore_overlayable)) {
       std::vector<std::string> create_args = {"--target-apk-path",  target_apk_path,
                                               "--overlay-apk-path", overlay.apk_path,
                                               "--idmap-path",       overlay.idmap_path};
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index d723776..15e22a3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -33,22 +33,29 @@
 #include "idmap2/BinaryStreamVisitor.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
-#include "idmap2/Policies.h"
+#include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
+#include "idmap2/ZipFile.h"
 #include "utils/String8.h"
 
 using android::IPCThreadState;
+using android::base::StringPrintf;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::GetPackageCrc;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
-using android::idmap2::PolicyBitmask;
+using android::idmap2::ZipFile;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
 using android::idmap2::utils::UidHasWriteAccessToPath;
 
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+
 namespace {
 
+constexpr const char* kFrameworkPath = "/system/framework/framework-res.apk";
+
 Status ok() {
   return Status::ok();
 }
@@ -62,6 +69,21 @@
   return static_cast<PolicyBitmask>(arg);
 }
 
+Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
+  const auto zip = ZipFile::Open(apk_path);
+  if (!zip) {
+    return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
+  }
+
+  const auto crc = GetPackageCrc(*zip);
+  if (!crc) {
+    return error(crc.GetErrorMessage());
+  }
+
+  *out_crc = *crc;
+  return ok();
+}
+
 }  // namespace
 
 namespace android::os {
@@ -93,21 +115,52 @@
   return ok();
 }
 
-Status Idmap2Service::verifyIdmap(const std::string& overlay_apk_path,
-                                  int32_t fulfilled_policies ATTRIBUTE_UNUSED,
-                                  bool enforce_overlayable ATTRIBUTE_UNUSED,
-                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
+                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+                                  bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
+                                  bool* _aidl_return) {
   SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
   assert(_aidl_return);
+
   const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
   fin.close();
-  *_aidl_return = header && header->IsUpToDate();
+  if (!header) {
+    *_aidl_return = false;
+    return error("failed to parse idmap header");
+  }
 
-  // TODO(b/119328308): Check that the set of fulfilled policies of the overlay has not changed
+  uint32_t target_crc;
+  if (target_apk_path == kFrameworkPath && android_crc_) {
+    target_crc = *android_crc_;
+  } else {
+    auto target_crc_status = GetCrc(target_apk_path, &target_crc);
+    if (!target_crc_status.isOk()) {
+      *_aidl_return = false;
+      return target_crc_status;
+    }
 
-  return ok();
+    // Loading the framework zip can take several milliseconds. Cache the crc of the framework
+    // resource APK to reduce repeated work during boot.
+    if (target_apk_path == kFrameworkPath) {
+      android_crc_ = target_crc;
+    }
+  }
+
+  uint32_t overlay_crc;
+  auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
+  if (!overlay_crc_status.isOk()) {
+    *_aidl_return = false;
+    return overlay_crc_status;
+  }
+
+  auto up_to_date =
+      header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc,
+                         ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
+
+  *_aidl_return = static_cast<bool>(up_to_date);
+  return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
 }
 
 Status Idmap2Service::createIdmap(const std::string& target_apk_path,
@@ -137,21 +190,27 @@
     return error("failed to load apk " + overlay_apk_path);
   }
 
-  const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
-                                          *overlay_apk, policy_bitmask, enforce_overlayable);
+  const auto idmap =
+      Idmap::FromApkAssets(*target_apk, *overlay_apk, policy_bitmask, enforce_overlayable);
   if (!idmap) {
     return error(idmap.GetErrorMessage());
   }
 
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected.
+  unlink(idmap_path.c_str());
+
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
     return error("failed to open idmap path " + idmap_path);
   }
+
   BinaryStreamVisitor visitor(fout);
   (*idmap)->accept(&visitor);
   fout.close();
   if (fout.fail()) {
+    unlink(idmap_path.c_str());
     return error("failed to write to idmap path " + idmap_path);
   }
 
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 73a236a..abee999 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -20,9 +20,6 @@
 #include <android-base/unique_fd.h>
 #include <binder/BinderService.h>
 
-#include <optional>
-#include <string>
-
 #include "android/os/BnIdmap2.h"
 
 namespace android::os {
@@ -34,18 +31,24 @@
   }
 
   binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
-                              std::string* _aidl_return);
+                              std::string* _aidl_return) override;
 
   binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
-                             bool* _aidl_return);
+                             bool* _aidl_return) override;
 
-  binder::Status verifyIdmap(const std::string& overlay_apk_path, int32_t fulfilled_policies,
-                             bool enforce_overlayable, int32_t user_id, bool* _aidl_return);
+  binder::Status verifyIdmap(const std::string& target_apk_path,
+                             const std::string& overlay_apk_path, int32_t fulfilled_policies,
+                             bool enforce_overlayable, int32_t user_id,
+                             bool* _aidl_return) override;
 
   binder::Status createIdmap(const std::string& target_apk_path,
                              const std::string& overlay_apk_path, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
-                             std::optional<std::string>* _aidl_return);
+                             std::optional<std::string>* _aidl_return) override;
+
+ private:
+  // Cache the crc of the android framework package since the crc cannot change without a reboot.
+  std::optional<uint32_t> android_crc_;
 };
 
 }  // namespace android::os
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
index cd474c0..156f1d7 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
@@ -20,18 +20,13 @@
  * @hide
  */
 interface IIdmap2 {
-  const int POLICY_PUBLIC = 0x00000001;
-  const int POLICY_SYSTEM_PARTITION = 0x00000002;
-  const int POLICY_VENDOR_PARTITION = 0x00000004;
-  const int POLICY_PRODUCT_PARTITION = 0x00000008;
-  const int POLICY_SIGNATURE = 0x00000010;
-  const int POLICY_ODM_PARTITION = 0x00000020;
-  const int POLICY_OEM_PARTITION = 0x00000040;
-
   @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
   boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
-  boolean verifyIdmap(@utf8InCpp String overlayApkPath, int fulfilledPolicies,
-                      boolean enforceOverlayable, int userId);
+  boolean verifyIdmap(@utf8InCpp String targetApkPath,
+					  @utf8InCpp String overlayApkPath,
+                      int fulfilledPolicies,
+                      boolean enforceOverlayable,
+                      int userId);
   @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
                                           @utf8InCpp String overlayApkPath,
                                           int fulfilledPolicies,
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
new file mode 100644
index 0000000..02b27a8
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.os;
+
+/**
+ * @see ResourcesTypes.h ResTable_overlayable_policy_header::PolicyFlags
+ * @hide
+ */
+interface OverlayablePolicy {
+  const int PUBLIC = 0x00000001;
+  const int SYSTEM_PARTITION = 0x00000002;
+  const int VENDOR_PARTITION = 0x00000004;
+  const int PRODUCT_PARTITION = 0x00000008;
+  const int SIGNATURE = 0x00000010;
+  const int ODM_PARTITION = 0x00000020;
+  const int OEM_PARTITION = 0x00000040;
+  const int ACTOR_SIGNATURE = 0x00000080;
+}
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 2c3e9d3..ff45b14 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -29,16 +29,19 @@
  public:
   explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~BinaryStreamVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
 
  private:
+  void Write(const void* value, size_t length);
+  void Write8(uint8_t value);
   void Write16(uint16_t value);
   void Write32(uint32_t value);
-  void WriteString(const StringPiece& value);
+  void WriteString256(const StringPiece& value);
+  void WriteString(const std::string& value);
   std::ostream& stream_;
 };
 
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index ebbb5ffc..0f05592 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -18,19 +18,21 @@
  * # idmap file format (current version)
  *
  * idmap             := header data*
- * header            := magic version target_crc overlay_crc target_path overlay_path
+ * header            := magic version target_crc overlay_crc target_path overlay_path debug_info
  * data              := data_header data_block*
  * data_header       := target_package_id types_count
  * data_block        := target_type overlay_type entry_count entry_offset entry*
- * overlay_path      := string
- * target_path       := string
+ * overlay_path      := string256
+ * target_path       := string256
+ * debug_info        := string
+ * string            := <uint32_t> <uint8_t>+ '\0'+
  * entry             := <uint32_t>
  * entry_count       := <uint16_t>
  * entry_offset      := <uint16_t>
  * magic             := <uint32_t>
  * overlay_crc       := <uint32_t>
  * overlay_type      := <uint16_t>
- * string            := <uint8_t>[256]
+ * string256         := <uint8_t>[256]
  * target_crc        := <uint32_t>
  * target_package_id := <uint16_t>
  * target_type       := <uint16_t>
@@ -41,6 +43,22 @@
  * # idmap file format changelog
  * ## v1
  * - Identical to idmap v1.
+ *
+ * ## v2
+ * - Entries are no longer separated by type into type specific data blocks.
+ * - Added overlay-indexed target resource id lookup capabilities.
+ * - Target and overlay entries are stored as a sparse array in the data block. The target entries
+ *   array maps from target resource id to overlay data type and value and the array is sorted by
+ *   target resource id. The overlay entries array maps from overlay resource id to target resource
+ *   id and the array is sorted by overlay resource id. It is important for both arrays to be sorted
+ *   to allow for O(log(number_of_overlaid_resources)) performance when looking up resource
+ *   mappings at runtime.
+ * - Idmap can now encode a type and value to override a resource without needing a table entry.
+ * - A string pool block is included to retrieve the value of strings that do not have a resource
+ *   table entry.
+ *
+ * ## v3
+ * - Add 'debug' block to IdmapHeader.
  */
 
 #ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
@@ -55,21 +73,15 @@
 #include "androidfw/ApkAssets.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
-#include "idmap2/Policies.h"
+#include "idmap2/ResourceMapping.h"
+#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
 class Idmap;
 class Visitor;
 
-// use typedefs to let the compiler warn us about implicit casts
-typedef uint32_t ResourceId;  // 0xpptteeee
-typedef uint8_t PackageId;    // pp in 0xpptteeee
-typedef uint8_t TypeId;       // tt in 0xpptteeee
-typedef uint16_t EntryId;     // eeee in 0xpptteeee
-
 static constexpr const ResourceId kPadding = 0xffffffffu;
-
 static constexpr const EntryId kNoEntry = 0xffffu;
 
 // magic number: all idmap files start with this
@@ -82,6 +94,9 @@
 // terminating null)
 static constexpr const size_t kIdmapStringLength = 256;
 
+// Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
+Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
+
 class IdmapHeader {
  public:
   static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
@@ -102,6 +117,14 @@
     return overlay_crc_;
   }
 
+  inline uint32_t GetFulfilledPolicies() const {
+    return fulfilled_policies_;
+  }
+
+  bool GetEnforceOverlayable() const {
+    return enforce_overlayable_;
+  }
+
   inline StringPiece GetTargetPath() const {
     return StringPiece(target_path_);
   }
@@ -110,10 +133,18 @@
     return StringPiece(overlay_path_);
   }
 
+  inline const std::string& GetDebugInfo() const {
+    return debug_info_;
+  }
+
   // Invariant: anytime the idmap data encoding is changed, the idmap version
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
-  Result<Unit> IsUpToDate() const;
+  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path,
+                          PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
+  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc,
+                          uint32_t overlay_crc, PolicyBitmask fulfilled_policies,
+                          bool enforce_overlayable) const;
 
   void accept(Visitor* v) const;
 
@@ -125,13 +156,15 @@
   uint32_t version_;
   uint32_t target_crc_;
   uint32_t overlay_crc_;
+  uint32_t fulfilled_policies_;
+  bool enforce_overlayable_;
   char target_path_[kIdmapStringLength];
   char overlay_path_[kIdmapStringLength];
+  std::string debug_info_;
 
   friend Idmap;
   DISALLOW_COPY_AND_ASSIGN(IdmapHeader);
 };
-
 class IdmapData {
  public:
   class Header {
@@ -142,70 +175,72 @@
       return target_package_id_;
     }
 
-    inline uint16_t GetTypeCount() const {
-      return type_count_;
+    inline PackageId GetOverlayPackageId() const {
+      return overlay_package_id_;
+    }
+
+    inline uint32_t GetTargetEntryCount() const {
+      return target_entry_count;
+    }
+
+    inline uint32_t GetOverlayEntryCount() const {
+      return overlay_entry_count;
+    }
+
+    inline uint32_t GetStringPoolIndexOffset() const {
+      return string_pool_index_offset;
+    }
+
+    inline uint32_t GetStringPoolLength() const {
+      return string_pool_len;
     }
 
     void accept(Visitor* v) const;
 
    private:
-    Header() {
-    }
-
     PackageId target_package_id_;
-    uint16_t type_count_;
+    PackageId overlay_package_id_;
+    uint32_t target_entry_count;
+    uint32_t overlay_entry_count;
+    uint32_t string_pool_index_offset;
+    uint32_t string_pool_len;
+    Header() = default;
 
     friend Idmap;
+    friend IdmapData;
     DISALLOW_COPY_AND_ASSIGN(Header);
   };
 
-  class TypeEntry {
-   public:
-    static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream);
+  struct TargetEntry {
+    ResourceId target_id;
+    TargetValue::DataType data_type;
+    TargetValue::DataValue data_value;
+  };
 
-    inline TypeId GetTargetTypeId() const {
-      return target_type_id_;
-    }
-
-    inline TypeId GetOverlayTypeId() const {
-      return overlay_type_id_;
-    }
-
-    inline uint16_t GetEntryCount() const {
-      return entries_.size();
-    }
-
-    inline uint16_t GetEntryOffset() const {
-      return entry_offset_;
-    }
-
-    inline EntryId GetEntry(size_t i) const {
-      return i < entries_.size() ? entries_[i] : 0xffffu;
-    }
-
-    void accept(Visitor* v) const;
-
-   private:
-    TypeEntry() {
-    }
-
-    TypeId target_type_id_;
-    TypeId overlay_type_id_;
-    uint16_t entry_offset_;
-    std::vector<EntryId> entries_;
-
-    friend Idmap;
-    DISALLOW_COPY_AND_ASSIGN(TypeEntry);
+  struct OverlayEntry {
+    ResourceId overlay_id;
+    ResourceId target_id;
   };
 
   static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream);
 
+  static Result<std::unique_ptr<const IdmapData>> FromResourceMapping(
+      const ResourceMapping& resource_mapping);
+
   inline const std::unique_ptr<const Header>& GetHeader() const {
     return header_;
   }
 
-  inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const {
-    return type_entries_;
+  inline const std::vector<TargetEntry>& GetTargetEntries() const {
+    return target_entries_;
+  }
+
+  inline const std::vector<OverlayEntry>& GetOverlayEntries() const {
+    return overlay_entries_;
+  }
+
+  inline const void* GetStringPoolData() const {
+    return string_pool_.get();
   }
 
   void accept(Visitor* v) const;
@@ -215,7 +250,9 @@
   }
 
   std::unique_ptr<const Header> header_;
-  std::vector<std::unique_ptr<const TypeEntry>> type_entries_;
+  std::vector<TargetEntry> target_entries_;
+  std::vector<OverlayEntry> overlay_entries_;
+  std::unique_ptr<uint8_t[]> string_pool_;
 
   friend Idmap;
   DISALLOW_COPY_AND_ASSIGN(IdmapData);
@@ -232,9 +269,7 @@
   // file is used; change this in the next version of idmap to use a named
   // package instead; also update FromApkAssets to take additional parameters:
   // the target and overlay package names
-  static Result<std::unique_ptr<const Idmap>> FromApkAssets(const std::string& target_apk_path,
-                                                            const ApkAssets& target_apk_assets,
-                                                            const std::string& overlay_apk_path,
+  static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
                                                             const ApkAssets& overlay_apk_assets,
                                                             const PolicyBitmask& fulfilled_policies,
                                                             bool enforce_overlayable);
@@ -267,7 +302,6 @@
   virtual void visit(const IdmapHeader& header) = 0;
   virtual void visit(const IdmapData& data) = 0;
   virtual void visit(const IdmapData::Header& header) = 0;
-  virtual void visit(const IdmapData::TypeEntry& type_entry) = 0;
 };
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/include/idmap2/LogInfo.h b/cmds/idmap2/include/idmap2/LogInfo.h
new file mode 100644
index 0000000..a6237e6
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/LogInfo.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
+#define IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
+
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#if __ANDROID__
+#include "android-base/logging.h"
+#else
+#include <iostream>
+#endif
+
+namespace android::idmap2 {
+
+class LogMessage {
+ public:
+  LogMessage() = default;
+
+  template <typename T>
+  LogMessage& operator<<(const T& value) {
+    stream_ << value;
+    return *this;
+  }
+
+  std::string GetString() const {
+    return stream_.str();
+  }
+
+ private:
+  std::stringstream stream_;
+};
+
+class LogInfo {
+ public:
+  LogInfo() = default;
+
+  inline void Info(const LogMessage& msg) {
+    lines_.push_back("I " + msg.GetString());
+  }
+
+  inline void Warning(const LogMessage& msg) {
+#ifdef __ANDROID__
+    LOG(WARNING) << msg.GetString();
+#else
+    std::cerr << "W " << msg.GetString() << std::endl;
+#endif
+    lines_.push_back("W " + msg.GetString());
+  }
+
+  inline std::string GetString() const {
+    std::ostringstream stream;
+    std::copy(lines_.begin(), lines_.end(), std::ostream_iterator<std::string>(stream, "\n"));
+    return stream.str();
+  }
+
+ private:
+  std::vector<std::string> lines_;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
diff --git a/cmds/idmap2/include/idmap2/Policies.h b/cmds/idmap2/include/idmap2/Policies.h
deleted file mode 100644
index 87edd35..0000000
--- a/cmds/idmap2/include/idmap2/Policies.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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 <string>
-#include <vector>
-
-#include "Result.h"
-#include "androidfw/ResourceTypes.h"
-#include "androidfw/StringPiece.h"
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
-#define IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
-
-namespace android::idmap2 {
-
-constexpr const char* kPolicyOdm = "odm";
-constexpr const char* kPolicyOem = "oem";
-constexpr const char* kPolicyProduct = "product";
-constexpr const char* kPolicyPublic = "public";
-constexpr const char* kPolicySignature = "signature";
-constexpr const char* kPolicySystem = "system";
-constexpr const char* kPolicyVendor = "vendor";
-
-using PolicyFlags = ResTable_overlayable_policy_header::PolicyFlags;
-using PolicyBitmask = uint32_t;
-
-// Parses the string representations of policies into a bitmask.
-Result<PolicyBitmask> PoliciesToBitmask(const std::vector<std::string>& policies);
-
-// Retrieves the string representations of policies in the bitmask.
-std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask);
-
-}  // namespace android::idmap2
-
-#endif  // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
diff --git a/cmds/idmap2/include/idmap2/PolicyUtils.h b/cmds/idmap2/include/idmap2/PolicyUtils.h
new file mode 100644
index 0000000..b95b8b4
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/PolicyUtils.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_
+#define IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+#include "idmap2/Policies.h"
+#include "idmap2/Result.h"
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+
+namespace android::idmap2::utils {
+
+// Returns a Result object containing a policy flag bitmask built from a list of policy strings.
+// On error will contain a human readable message listing the invalid policies.
+Result<PolicyBitmask> PoliciesToBitmaskResult(const std::vector<std::string>& policies);
+
+// Converts a bitmask of policy flags into a list of their string representation as would be written
+// into XML
+std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask);
+
+}  // namespace android::idmap2::utils
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 5111bb2..5dcf217 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -33,17 +33,16 @@
  public:
   explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~PrettyPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
 
  private:
   std::ostream& stream_;
   std::unique_ptr<const ApkAssets> target_apk_;
   AssetManager2 target_am_;
-  PackageId last_seen_package_id_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 2e543d4..92c1864 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -34,22 +34,25 @@
  public:
   explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~RawPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
 
  private:
+  void print(uint8_t value, const char* fmt, ...);
   void print(uint16_t value, const char* fmt, ...);
   void print(uint32_t value, const char* fmt, ...);
-  void print(const std::string& value, const char* fmt, ...);
+  void print(const std::string& value, size_t encoded_size, const char* fmt, ...);
+  void print_raw(uint32_t length, const char* fmt, ...);
 
   std::ostream& stream_;
   std::unique_ptr<const ApkAssets> target_apk_;
+  std::unique_ptr<const ApkAssets> overlay_apk_;
   AssetManager2 target_am_;
+  AssetManager2 overlay_am_;
   size_t offset_;
-  PackageId last_seen_package_id_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
new file mode 100644
index 0000000..5869409
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "androidfw/ApkAssets.h"
+#include "idmap2/LogInfo.h"
+#include "idmap2/Policies.h"
+#include "idmap2/ResourceUtils.h"
+#include "idmap2/Result.h"
+#include "idmap2/XmlParser.h"
+
+using android::idmap2::utils::OverlayManifestInfo;
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+
+namespace android::idmap2 {
+
+struct TargetValue {
+  typedef uint8_t DataType;
+  typedef uint32_t DataValue;
+  DataType data_type;
+  DataValue data_value;
+};
+
+using TargetResourceMap = std::map<ResourceId, TargetValue>;
+using OverlayResourceMap = std::map<ResourceId, ResourceId>;
+
+class ResourceMapping {
+ public:
+  // Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
+  // `false` disables all overlayable and policy enforcement: this is intended for backwards
+  // compatibility pre-Q and unit tests.
+  static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
+                                               const ApkAssets& overlay_apk_assets,
+                                               const OverlayManifestInfo& overlay_info,
+                                               const PolicyBitmask& fulfilled_policies,
+                                               bool enforce_overlayable, LogInfo& log_info);
+
+  // Retrieves the mapping of target resource id to overlay value.
+  inline TargetResourceMap GetTargetToOverlayMap() const {
+    return target_map_;
+  }
+
+  // Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
+  // an overlay resource to appear as a reference to its corresponding target resource at runtime.
+  OverlayResourceMap GetOverlayToTargetMap() const;
+
+  // Retrieves the build-time package id of the target package.
+  inline uint32_t GetTargetPackageId() const {
+    return target_package_id_;
+  }
+
+  // Retrieves the build-time package id of the overlay package.
+  inline uint32_t GetOverlayPackageId() const {
+    return overlay_package_id_;
+  }
+
+  // Retrieves the offset that was added to the index of inline string overlay values so the indices
+  // do not collide with the indices of the overlay resource table string pool.
+  inline uint32_t GetStringPoolOffset() const {
+    return string_pool_offset_;
+  }
+
+  // Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
+  inline const std::pair<const uint8_t*, uint32_t> GetStringPoolData() const {
+    return std::make_pair(string_pool_data_.get(), string_pool_data_length_);
+  }
+
+ private:
+  ResourceMapping() = default;
+
+  // Apps a mapping of target resource id to the type and value of the data that overlays the
+  // target resource. The data_type is the runtime format of the data value (see
+  // Res_value::dataType). If rewrite_overlay_reference is `true` then references to an overlay
+  // resource should appear as a reference to its corresponding target resource at runtime.
+  Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
+                          TargetValue::DataValue data_value, bool rewrite_overlay_reference);
+
+  // Removes the overlay value mapping for the target resource.
+  void RemoveMapping(ResourceId target_resource);
+
+  // Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
+  static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
+                                                       const LoadedPackage* target_package,
+                                                       const LoadedPackage* overlay_package,
+                                                       size_t string_pool_offset,
+                                                       const XmlParser& overlay_parser,
+                                                       LogInfo& log_info);
+
+  // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
+  // a target resource, a resource must exist in the overlay with the same type and entry name as
+  // the target resource.
+  static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
+                                                             const AssetManager2* overlay_am,
+                                                             const LoadedPackage* target_package,
+                                                             const LoadedPackage* overlay_package);
+
+  // Removes resources that do not pass policy or overlayable checks of the target package.
+  void FilterOverlayableResources(const AssetManager2* target_am,
+                                  const LoadedPackage* target_package,
+                                  const LoadedPackage* overlay_package,
+                                  const OverlayManifestInfo& overlay_info,
+                                  const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
+
+  TargetResourceMap target_map_;
+  std::multimap<ResourceId, ResourceId> overlay_map_;
+
+  uint32_t target_package_id_ = 0;
+  uint32_t overlay_package_id_ = 0;
+  uint32_t string_pool_offset_ = 0;
+  uint32_t string_pool_data_length_ = 0;
+  std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index 9a0c2ab..c643b0e 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -21,19 +21,36 @@
 #include <string>
 
 #include "androidfw/AssetManager2.h"
-#include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/ZipFile.h"
 
-namespace android::idmap2::utils {
+namespace android::idmap2 {
+
+// use typedefs to let the compiler warn us about implicit casts
+typedef uint32_t ResourceId;  // 0xpptteeee
+typedef uint8_t PackageId;    // pp in 0xpptteeee
+typedef uint8_t TypeId;       // tt in 0xpptteeee
+typedef uint16_t EntryId;     // eeee in 0xpptteeee
+
+#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
+#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
+
+namespace utils {
+
+// Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
+bool IsReference(uint8_t data_type);
+
+// Converts the Res_value::data_type to a human-readable string representation.
+StringPiece DataTypeToString(uint8_t data_type);
 
 struct OverlayManifestInfo {
-  std::string target_package;  // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_name;     // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string requiredSystemPropertyName;  // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_package;               // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_name;                  // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string requiredSystemPropertyName;   // NOLINT(misc-non-private-member-variables-in-classes)
   std::string requiredSystemPropertyValue;  // NOLINT(misc-non-private-member-variables-in-classes)
-  bool is_static;              // NOLINT(misc-non-private-member-variables-in-classes)
-  int priority = -1;           // NOLINT(misc-non-private-member-variables-in-classes)
+  uint32_t resource_mapping;                // NOLINT(misc-non-private-member-variables-in-classes)
+  bool is_static;                           // NOLINT(misc-non-private-member-variables-in-classes)
+  int priority = -1;                        // NOLINT(misc-non-private-member-variables-in-classes)
 };
 
 Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
@@ -41,6 +58,8 @@
 
 Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
 
-}  // namespace android::idmap2::utils
+}  // namespace utils
+
+}  // namespace android::idmap2
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h
deleted file mode 100644
index dd89dee..0000000
--- a/cmds/idmap2/include/idmap2/Xml.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_
-#define IDMAP2_INCLUDE_IDMAP2_XML_H_
-
-#include <map>
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-#include "utils/String16.h"
-
-namespace android::idmap2 {
-
-class Xml {
- public:
-  static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false);
-
-  std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const;
-
-  ~Xml();
-
- private:
-  Xml() {
-  }
-
-  mutable ResXMLTree xml_;
-
-  DISALLOW_COPY_AND_ASSIGN(Xml);
-};
-
-}  // namespace android::idmap2
-
-#endif  // IDMAP2_INCLUDE_IDMAP2_XML_H_
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
new file mode 100644
index 0000000..972a6d7
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
+#define IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "Result.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/String16.h"
+
+namespace android::idmap2 {
+
+class XmlParser {
+ public:
+  using Event = ResXMLParser::event_code_t;
+  class iterator;
+
+  class Node {
+   public:
+    Event event() const;
+    std::string name() const;
+
+    Result<std::string> GetAttributeStringValue(const std::string& name) const;
+    Result<Res_value> GetAttributeValue(const std::string& name) const;
+
+    bool operator==(const Node& rhs) const;
+    bool operator!=(const Node& rhs) const;
+
+   private:
+    explicit Node(const ResXMLTree& tree);
+    Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos);
+
+    // Retrieves/Sets the position of the position of the xml parser in the xml tree.
+    ResXMLParser::ResXMLPosition get_position() const;
+    void set_position(const ResXMLParser::ResXMLPosition& pos);
+
+    // If `inner_child` is true, seek advances the parser to the first inner child of the current
+    // node. Otherwise, seek advances the parser to the following node. Returns false if there is
+    // no node to seek to.
+    bool Seek(bool inner_child);
+
+    ResXMLParser parser_;
+    friend iterator;
+  };
+
+  class iterator {
+   public:
+    iterator(const iterator& other) : iterator(other.tree_, other.iter_) {
+    }
+
+    inline iterator& operator=(const iterator& rhs) {
+      iter_.set_position(rhs.iter_.get_position());
+      return *this;
+    }
+
+    inline bool operator==(const iterator& rhs) const {
+      return iter_ == rhs.iter_;
+    }
+
+    inline bool operator!=(const iterator& rhs) const {
+      return !(*this == rhs);
+    }
+
+    inline iterator operator++() {
+      // Seek to the following xml node.
+      iter_.Seek(false /* inner_child */);
+      return *this;
+    }
+
+    iterator begin() const {
+      iterator child_it(*this);
+      // Seek to the first inner child of the current node.
+      child_it.iter_.Seek(true /* inner_child */);
+      return child_it;
+    }
+
+    iterator end() const {
+      iterator child_it = begin();
+      while (child_it.iter_.Seek(false /* inner_child */)) {
+        // Continue iterating until the end tag is found.
+      }
+
+      return child_it;
+    }
+
+    inline const Node operator*() {
+      return Node(tree_, iter_.get_position());
+    }
+
+    inline const Node* operator->() {
+      return &iter_;
+    }
+
+   private:
+    explicit iterator(const ResXMLTree& tree) : tree_(tree), iter_(Node(tree)) {
+    }
+    iterator(const ResXMLTree& tree, const Node& node)
+        : tree_(tree), iter_(Node(tree, node.get_position())) {
+    }
+
+    const ResXMLTree& tree_;
+    Node iter_;
+    friend XmlParser;
+  };
+
+  // Creates a new xml parser beginning at the first tag.
+  static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
+                                                         bool copy_data = false);
+  ~XmlParser();
+
+  inline iterator tree_iterator() const {
+    return iterator(tree_);
+  }
+
+  inline const ResStringPool& get_strings() const {
+    return tree_.getStrings();
+  }
+
+ private:
+  XmlParser() = default;
+  mutable ResXMLTree tree_;
+
+  DISALLOW_COPY_AND_ASSIGN(XmlParser);
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index dee2d21..255212a 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -24,6 +24,14 @@
 
 namespace android::idmap2 {
 
+void BinaryStreamVisitor::Write(const void* value, size_t length) {
+  stream_.write(reinterpret_cast<const char*>(value), length);
+}
+
+void BinaryStreamVisitor::Write8(uint8_t value) {
+  stream_.write(reinterpret_cast<char*>(&value), sizeof(uint8_t));
+}
+
 void BinaryStreamVisitor::Write16(uint16_t value) {
   uint16_t x = htodl(value);
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t));
@@ -34,13 +42,21 @@
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
 }
 
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString256(const StringPiece& value) {
   char buf[kIdmapStringLength];
   memset(buf, 0, sizeof(buf));
   memcpy(buf, value.data(), std::min(value.size(), sizeof(buf)));
   stream_.write(buf, sizeof(buf));
 }
 
+void BinaryStreamVisitor::WriteString(const std::string& value) {
+  // pad with null to nearest word boundary; include at least one terminating null
+  size_t padding_size = 4 - (value.size() % 4);
+  Write32(value.size() + padding_size);
+  stream_.write(value.c_str(), value.size());
+  stream_.write("\0\0\0\0", padding_size);
+}
+
 void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
   // nothing to do
 }
@@ -50,30 +66,35 @@
   Write32(header.GetVersion());
   Write32(header.GetTargetCrc());
   Write32(header.GetOverlayCrc());
-  WriteString(header.GetTargetPath());
-  WriteString(header.GetOverlayPath());
+  Write32(header.GetFulfilledPolicies());
+  Write8(static_cast<uint8_t>(header.GetEnforceOverlayable()));
+  WriteString256(header.GetTargetPath());
+  WriteString256(header.GetOverlayPath());
+  WriteString(header.GetDebugInfo());
 }
 
-void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
-  // nothing to do
+void BinaryStreamVisitor::visit(const IdmapData& data) {
+  for (const auto& target_entry : data.GetTargetEntries()) {
+    Write32(target_entry.target_id);
+    Write8(target_entry.data_type);
+    Write32(target_entry.data_value);
+  }
+
+  for (const auto& overlay_entry : data.GetOverlayEntries()) {
+    Write32(overlay_entry.overlay_id);
+    Write32(overlay_entry.target_id);
+  }
+
+  Write(data.GetStringPoolData(), data.GetHeader()->GetStringPoolLength());
 }
 
 void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
-  Write16(header.GetTargetPackageId());
-  Write16(header.GetTypeCount());
-}
-
-void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& type_entry) {
-  const uint16_t entryCount = type_entry.GetEntryCount();
-
-  Write16(type_entry.GetTargetTypeId());
-  Write16(type_entry.GetOverlayTypeId());
-  Write16(entryCount);
-  Write16(type_entry.GetEntryOffset());
-  for (uint16_t i = 0; i < entryCount; i++) {
-    EntryId entry_id = type_entry.GetEntry(i);
-    Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding);
-  }
+  Write8(header.GetTargetPackageId());
+  Write8(header.GetOverlayPackageId());
+  Write32(header.GetTargetEntryCount());
+  Write32(header.GetOverlayEntryCount());
+  Write32(header.GetStringPoolIndexOffset());
+  Write32(header.GetStringPoolLength());
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 4649675..23c25a7 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -30,6 +30,7 @@
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/AssetManager2.h"
+#include "idmap2/ResourceMapping.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
@@ -41,34 +42,10 @@
 
 namespace {
 
-#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
-
-#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
-
-class MatchingResources {
- public:
-  void Add(ResourceId target_resid, ResourceId overlay_resid) {
-    TypeId target_typeid = EXTRACT_TYPE(target_resid);
-    if (map_.find(target_typeid) == map_.end()) {
-      map_.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>());
-    }
-    map_[target_typeid].insert(std::make_pair(target_resid, overlay_resid));
-  }
-
-  inline const std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>>& WARN_UNUSED
-  Map() const {
-    return map_;
-  }
-
- private:
-  // target type id -> set { pair { overlay entry id, overlay entry id } }
-  std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map_;
-};
-
-bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) {
-  uint16_t value;
-  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) {
-    *out = dtohl(value);
+bool WARN_UNUSED Read8(std::istream& stream, uint8_t* out) {
+  uint8_t value;
+  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint8_t))) {
+    *out = value;
     return true;
   }
   return false;
@@ -83,8 +60,17 @@
   return false;
 }
 
+bool WARN_UNUSED ReadBuffer(std::istream& stream, std::unique_ptr<uint8_t[]>* out, size_t length) {
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]);
+  if (stream.read(reinterpret_cast<char*>(buffer.get()), length)) {
+    *out = std::move(buffer);
+    return true;
+  }
+  return false;
+}
+
 // a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
-bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) {
+bool WARN_UNUSED ReadString256(std::istream& stream, char out[kIdmapStringLength]) {
   char buf[kIdmapStringLength];
   memset(buf, 0, sizeof(buf));
   if (!stream.read(buf, sizeof(buf))) {
@@ -97,29 +83,26 @@
   return true;
 }
 
-ResourceId NameToResid(const AssetManager2& am, const std::string& name) {
-  return am.GetResourceId(name);
-}
-
-// TODO(martenkongstad): scan for package name instead of assuming package at index 0
-//
-// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
-// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
-// this assumption tends to work out. That said, the correct thing to do is to scan
-// resources.arsc for a package with a given name as read from the package manifest instead of
-// relying on a hard-coded index. This however requires storing the package name in the idmap
-// header, which in turn requires incrementing the idmap version. Because the initial version of
-// idmap2 is compatible with idmap, this will have to wait for now.
-const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
-  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
-  if (packages.empty()) {
-    return nullptr;
+Result<std::string> ReadString(std::istream& stream) {
+  uint32_t size;
+  if (!Read32(stream, &size)) {
+    return Error("failed to read string size");
   }
-  int id = packages[0]->GetPackageId();
-  return loaded_arsc.GetPackageById(id);
+  if (size == 0) {
+    return std::string("");
+  }
+  std::string buf(size, '\0');
+  if (!stream.read(buf.data(), size)) {
+    return Error("failed to read string of size %u", size);
+  }
+  // buf is guaranteed to be null terminated (with enough nulls to end on a word boundary)
+  buf.resize(strlen(buf.c_str()));
+  return buf;
 }
 
-Result<uint32_t> GetCrc(const ZipFile& zip) {
+}  // namespace
+
+Result<uint32_t> GetPackageCrc(const ZipFile& zip) {
   const Result<uint32_t> a = zip.Crc("resources.arsc");
   const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
   return a && b
@@ -127,22 +110,59 @@
              : Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc");
 }
 
-}  // namespace
-
 std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
-
+  uint8_t enforce_overlayable;
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
       !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
-      !ReadString(stream, idmap_header->target_path_) ||
-      !ReadString(stream, idmap_header->overlay_path_)) {
+      !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) ||
+      !ReadString256(stream, idmap_header->target_path_) ||
+      !ReadString256(stream, idmap_header->overlay_path_)) {
     return nullptr;
   }
 
+  idmap_header->enforce_overlayable_ = static_cast<bool>(enforce_overlayable);
+
+  auto debug_str = ReadString(stream);
+  if (!debug_str) {
+    return nullptr;
+  }
+  idmap_header->debug_info_ = std::move(*debug_str);
+
   return std::move(idmap_header);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate() const {
+Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
+                                     PolicyBitmask fulfilled_policies,
+                                     bool enforce_overlayable) const {
+  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
+  if (!target_zip) {
+    return Error("failed to open target %s", target_path);
+  }
+
+  const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
+  if (!target_crc) {
+    return Error("failed to get target crc");
+  }
+
+  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
+  if (!overlay_zip) {
+    return Error("failed to overlay target %s", overlay_path);
+  }
+
+  const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
+  if (!overlay_crc) {
+    return Error("failed to get overlay crc");
+  }
+
+  return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies,
+                    enforce_overlayable);
+}
+
+Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
+                                     uint32_t target_crc, uint32_t overlay_crc,
+                                     PolicyBitmask fulfilled_policies,
+                                     bool enforce_overlayable) const {
   if (magic_ != kIdmapMagic) {
     return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic);
   }
@@ -151,34 +171,34 @@
     return Error("bad version: actual 0x%08x, expected 0x%08x", version_, kIdmapCurrentVersion);
   }
 
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_);
-  if (!target_zip) {
-    return Error("failed to open target %s", GetTargetPath().to_string().c_str());
-  }
-
-  Result<uint32_t> target_crc = GetCrc(*target_zip);
-  if (!target_crc) {
-    return Error("failed to get target crc");
-  }
-
-  if (target_crc_ != *target_crc) {
+  if (target_crc_ != target_crc) {
     return Error("bad target crc: idmap version 0x%08x, file system version 0x%08x", target_crc_,
-                 *target_crc);
+                 target_crc);
   }
 
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_);
-  if (!overlay_zip) {
-    return Error("failed to open overlay %s", GetOverlayPath().to_string().c_str());
-  }
-
-  Result<uint32_t> overlay_crc = GetCrc(*overlay_zip);
-  if (!overlay_crc) {
-    return Error("failed to get overlay crc");
-  }
-
-  if (overlay_crc_ != *overlay_crc) {
+  if (overlay_crc_ != overlay_crc) {
     return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_,
-                 *overlay_crc);
+                 overlay_crc);
+  }
+
+  if (fulfilled_policies_ != fulfilled_policies) {
+    return Error("bad fulfilled policies: idmap version 0x%08x, file system version 0x%08x",
+                 fulfilled_policies, fulfilled_policies_);
+  }
+
+  if (enforce_overlayable != enforce_overlayable_) {
+    return Error("bad enforce overlayable: idmap version %s, file system version %s",
+                 enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false");
+  }
+
+  if (strcmp(target_path, target_path_) != 0) {
+    return Error("bad target path: idmap version %s, file system version %s", target_path,
+                 target_path_);
+  }
+
+  if (strcmp(overlay_path, overlay_path_) != 0) {
+    return Error("bad overlay path: idmap version %s, file system version %s", overlay_path,
+                 overlay_path_);
   }
 
   return Unit{};
@@ -187,51 +207,48 @@
 std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
 
-  uint16_t target_package_id16;
-  if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) {
+  if (!Read8(stream, &idmap_data_header->target_package_id_) ||
+      !Read8(stream, &idmap_data_header->overlay_package_id_) ||
+      !Read32(stream, &idmap_data_header->target_entry_count) ||
+      !Read32(stream, &idmap_data_header->overlay_entry_count) ||
+      !Read32(stream, &idmap_data_header->string_pool_index_offset) ||
+      !Read32(stream, &idmap_data_header->string_pool_len)) {
     return nullptr;
   }
-  idmap_data_header->target_package_id_ = target_package_id16;
 
   return std::move(idmap_data_header);
 }
 
-std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream(
-    std::istream& stream) {
-  std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry());
-  uint16_t target_type16;
-  uint16_t overlay_type16;
-  uint16_t entry_count;
-  if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) ||
-      !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) {
-    return nullptr;
-  }
-  data->target_type_id_ = target_type16;
-  data->overlay_type_id_ = overlay_type16;
-  for (uint16_t i = 0; i < entry_count; i++) {
-    ResourceId resid;
-    if (!Read32(stream, &resid)) {
-      return nullptr;
-    }
-    data->entries_.push_back(resid);
-  }
-
-  return std::move(data);
-}
-
 std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapData> data(new IdmapData());
   data->header_ = IdmapData::Header::FromBinaryStream(stream);
   if (!data->header_) {
     return nullptr;
   }
-  for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) {
-    std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream);
-    if (!type) {
+  // Read the mapping of target resource id to overlay resource value.
+  for (size_t i = 0; i < data->header_->GetTargetEntryCount(); i++) {
+    TargetEntry target_entry{};
+    if (!Read32(stream, &target_entry.target_id) || !Read8(stream, &target_entry.data_type) ||
+        !Read32(stream, &target_entry.data_value)) {
       return nullptr;
     }
-    data->type_entries_.push_back(std::move(type));
+    data->target_entries_.emplace_back(target_entry);
   }
+
+  // Read the mapping of overlay resource id to target resource id.
+  for (size_t i = 0; i < data->header_->GetOverlayEntryCount(); i++) {
+    OverlayEntry overlay_entry{};
+    if (!Read32(stream, &overlay_entry.overlay_id) || !Read32(stream, &overlay_entry.target_id)) {
+      return nullptr;
+    }
+    data->overlay_entries_.emplace_back(overlay_entry);
+  }
+
+  // Read raw string pool bytes.
+  if (!ReadBuffer(stream, &data->string_pool_, data->header_->string_pool_len)) {
+    return nullptr;
+  }
+
   return std::move(data);
 }
 
@@ -266,95 +283,45 @@
   return {std::move(idmap)};
 }
 
-std::string ConcatPolicies(const std::vector<std::string>& policies) {
-  std::string message;
-  for (const std::string& policy : policies) {
-    if (!message.empty()) {
-      message.append("|");
-    }
-    message.append(policy);
+Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping(
+    const ResourceMapping& resource_mapping) {
+  if (resource_mapping.GetTargetToOverlayMap().empty()) {
+    return Error("no resources were overlaid");
   }
 
-  return message;
+  std::unique_ptr<IdmapData> data(new IdmapData());
+  for (const auto& mappings : resource_mapping.GetTargetToOverlayMap()) {
+    data->target_entries_.emplace_back(IdmapData::TargetEntry{
+        mappings.first, mappings.second.data_type, mappings.second.data_value});
+  }
+
+  for (const auto& mappings : resource_mapping.GetOverlayToTargetMap()) {
+    data->overlay_entries_.emplace_back(IdmapData::OverlayEntry{mappings.first, mappings.second});
+  }
+
+  std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
+  data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
+  data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId();
+  data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
+  data_header->overlay_entry_count = static_cast<uint32_t>(data->overlay_entries_.size());
+  data_header->string_pool_index_offset = resource_mapping.GetStringPoolOffset();
+
+  const auto string_pool_data = resource_mapping.GetStringPoolData();
+  data_header->string_pool_len = string_pool_data.second;
+  data->string_pool_ = std::unique_ptr<uint8_t[]>(new uint8_t[data_header->string_pool_len]);
+  memcpy(data->string_pool_.get(), string_pool_data.first, data_header->string_pool_len);
+
+  data->header_ = std::move(data_header);
+  return {std::move(data)};
 }
 
-Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
-                              const utils::OverlayManifestInfo& overlay_info,
-                              const PolicyBitmask& fulfilled_policies, const ResourceId& resid) {
-  static constexpr const PolicyBitmask sDefaultPolicies =
-      PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION |
-      PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION |
-      PolicyFlags::POLICY_PRODUCT_PARTITION | PolicyFlags::POLICY_SIGNATURE;
-
-  // If the resource does not have an overlayable definition, allow the resource to be overlaid if
-  // the overlay is preinstalled or signed with the same signature as the target.
-  if (!target_package.DefinesOverlayable()) {
-    return (sDefaultPolicies & fulfilled_policies) != 0
-               ? Result<Unit>({})
-               : Error(
-                     "overlay must be preinstalled or signed with the same signature as the "
-                     "target");
-  }
-
-  const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(resid);
-  if (overlayable_info == nullptr) {
-    // Do not allow non-overlayable resources to be overlaid.
-    return Error("resource has no overlayable declaration");
-  }
-
-  if (overlay_info.target_name != overlayable_info->name) {
-    // If the overlay supplies a target overlayable name, the resource must belong to the
-    // overlayable defined with the specified name to be overlaid.
-    return Error("<overlay> android:targetName '%s' does not match overlayable name '%s'",
-                 overlay_info.target_name.c_str(), overlayable_info->name.c_str());
-  }
-
-  // Enforce policy restrictions if the resource is declared as overlayable.
-  if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
-    return Error("overlay with policies '%s' does not fulfill any overlayable policies '%s'",
-                 ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
-                 ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
-  }
-
-  return Result<Unit>({});
-}
-
-Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& target_apk_path,
-                                                          const ApkAssets& target_apk_assets,
-                                                          const std::string& overlay_apk_path,
+Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
                                                           const ApkAssets& overlay_apk_assets,
                                                           const PolicyBitmask& fulfilled_policies,
                                                           bool enforce_overlayable) {
   SYSTRACE << "Idmap::FromApkAssets";
-  AssetManager2 target_asset_manager;
-  if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) {
-    return Error("failed to create target asset manager");
-  }
-
-  AssetManager2 overlay_asset_manager;
-  if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) {
-    return Error("failed to create overlay asset manager");
-  }
-
-  const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
-  if (target_arsc == nullptr) {
-    return Error("failed to load target resources.arsc");
-  }
-
-  const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
-  if (overlay_arsc == nullptr) {
-    return Error("failed to load overlay resources.arsc");
-  }
-
-  const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
-  if (target_pkg == nullptr) {
-    return Error("failed to load target package from resources.arsc");
-  }
-
-  const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
-  if (overlay_pkg == nullptr) {
-    return Error("failed to load overlay package from resources.arsc");
-  }
+  const std::string& target_apk_path = target_apk_assets.GetPath();
+  const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
 
   const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
   if (!target_zip) {
@@ -366,27 +333,25 @@
     return Error("failed to open overlay as zip");
   }
 
-  auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
-  if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-
   std::unique_ptr<IdmapHeader> header(new IdmapHeader());
   header->magic_ = kIdmapMagic;
   header->version_ = kIdmapCurrentVersion;
 
-  Result<uint32_t> crc = GetCrc(*target_zip);
+  Result<uint32_t> crc = GetPackageCrc(*target_zip);
   if (!crc) {
     return Error(crc.GetError(), "failed to get zip CRC for target");
   }
   header->target_crc_ = *crc;
 
-  crc = GetCrc(*overlay_zip);
+  crc = GetPackageCrc(*overlay_zip);
   if (!crc) {
     return Error(crc.GetError(), "failed to get zip CRC for overlay");
   }
   header->overlay_crc_ = *crc;
 
+  header->fulfilled_policies_ = fulfilled_policies;
+  header->enforce_overlayable_ = enforce_overlayable;
+
   if (target_apk_path.size() > sizeof(header->target_path_)) {
     return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(),
                  sizeof(header->target_path_));
@@ -395,78 +360,34 @@
   memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
 
   if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
-    return Error("overlay apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(),
+    return Error("overlay apk path \"%s\" longer than maximum size %zu", overlay_apk_path.c_str(),
                  sizeof(header->target_path_));
   }
   memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
   memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
 
+  auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
+  if (!overlay_info) {
+    return overlay_info.GetError();
+  }
+
+  LogInfo log_info;
+  auto resource_mapping =
+      ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info,
+                                     fulfilled_policies, enforce_overlayable, log_info);
+  if (!resource_mapping) {
+    return resource_mapping.GetError();
+  }
+
+  auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);
+  if (!idmap_data) {
+    return idmap_data.GetError();
+  }
+
   std::unique_ptr<Idmap> idmap(new Idmap());
+  header->debug_info_ = log_info.GetString();
   idmap->header_ = std::move(header);
-
-  // find the resources that exist in both packages
-  MatchingResources matching_resources;
-  const auto end = overlay_pkg->end();
-  for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
-    const ResourceId overlay_resid = *iter;
-    Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
-    if (!name) {
-      continue;
-    }
-    // prepend "<package>:" to turn name into "<package>:<type>/<name>"
-    const std::string full_name =
-        base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str());
-    const ResourceId target_resid = NameToResid(target_asset_manager, full_name);
-    if (target_resid == 0) {
-      continue;
-    }
-
-    if (enforce_overlayable) {
-      Result<Unit> success =
-          CheckOverlayable(*target_pkg, *overlay_info, fulfilled_policies, target_resid);
-      if (!success) {
-        LOG(WARNING) << "overlay \"" << overlay_apk_path
-                     << "\" is not allowed to overlay resource \"" << full_name
-                     << "\": " << success.GetErrorMessage();
-        continue;
-      }
-    }
-
-    matching_resources.Add(target_resid, overlay_resid);
-  }
-
-  if (matching_resources.Map().empty()) {
-    return Error("overlay \"%s\" does not successfully overlay any resource",
-                 overlay_apk_path.c_str());
-  }
-
-  // encode idmap data
-  std::unique_ptr<IdmapData> data(new IdmapData());
-  const auto types_end = matching_resources.Map().cend();
-  for (auto ti = matching_resources.Map().cbegin(); ti != types_end; ++ti) {
-    auto ei = ti->second.cbegin();
-    std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
-    type->target_type_id_ = EXTRACT_TYPE(ei->first);
-    type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
-    type->entry_offset_ = EXTRACT_ENTRY(ei->first);
-    EntryId last_target_entry = kNoEntry;
-    for (; ei != ti->second.cend(); ++ei) {
-      if (last_target_entry != kNoEntry) {
-        int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
-        type->entries_.insert(type->entries_.end(), count, kNoEntry);
-      }
-      type->entries_.push_back(EXTRACT_ENTRY(ei->second));
-      last_target_entry = EXTRACT_ENTRY(ei->first);
-    }
-    data->type_entries_.push_back(std::move(type));
-  }
-
-  std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
-  data_header->target_package_id_ = target_pkg->GetPackageId();
-  data_header->type_count_ = data->type_entries_.size();
-  data->header_ = std::move(data_header);
-
-  idmap->data_.push_back(std::move(data));
+  idmap->data_.push_back(std::move(*idmap_data));
 
   return {std::move(idmap)};
 }
@@ -481,25 +402,16 @@
   v->visit(*this);
 }
 
-void IdmapData::TypeEntry::accept(Visitor* v) const {
-  assert(v != nullptr);
-  v->visit(*this);
-}
-
 void IdmapData::accept(Visitor* v) const {
   assert(v != nullptr);
-  v->visit(*this);
   header_->accept(v);
-  auto end = type_entries_.cend();
-  for (auto iter = type_entries_.cbegin(); iter != end; ++iter) {
-    (*iter)->accept(v);
-  }
+  v->visit(*this);
 }
 
 void Idmap::accept(Visitor* v) const {
   assert(v != nullptr);
-  v->visit(*this);
   header_->accept(v);
+  v->visit(*this);
   auto end = data_.cend();
   for (auto iter = data_.cbegin(); iter != end; ++iter) {
     (*iter)->accept(v);
diff --git a/cmds/idmap2/libidmap2/Policies.cpp b/cmds/idmap2/libidmap2/Policies.cpp
deleted file mode 100644
index 495fe61..0000000
--- a/cmds/idmap2/libidmap2/Policies.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 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 "idmap2/Policies.h"
-
-#include <iterator>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "androidfw/ResourceTypes.h"
-#include "idmap2/Idmap.h"
-#include "idmap2/Result.h"
-
-namespace android::idmap2 {
-
-Result<PolicyBitmask> PoliciesToBitmask(const std::vector<std::string>& policies) {
-  static const std::unordered_map<android::StringPiece, PolicyFlags> kStringToFlag = {
-      {kPolicyOdm, PolicyFlags::POLICY_ODM_PARTITION},
-      {kPolicyOem, PolicyFlags::POLICY_OEM_PARTITION},
-      {kPolicyPublic, PolicyFlags::POLICY_PUBLIC},
-      {kPolicyProduct, PolicyFlags::POLICY_PRODUCT_PARTITION},
-      {kPolicySignature, PolicyFlags::POLICY_SIGNATURE},
-      {kPolicySystem, PolicyFlags::POLICY_SYSTEM_PARTITION},
-      {kPolicyVendor, PolicyFlags::POLICY_VENDOR_PARTITION},
-  };
-
-  PolicyBitmask bitmask = 0;
-  for (const std::string& policy : policies) {
-    const auto iter = kStringToFlag.find(policy);
-    if (iter != kStringToFlag.end()) {
-      bitmask |= iter->second;
-    } else {
-      return Error("unknown policy \"%s\"", policy.c_str());
-    }
-  }
-
-  return Result<PolicyBitmask>(bitmask);
-}
-
-std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) {
-  std::vector<std::string> policies;
-
-  if ((bitmask & PolicyFlags::POLICY_ODM_PARTITION) != 0) {
-    policies.emplace_back(kPolicyOdm);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_OEM_PARTITION) != 0) {
-    policies.emplace_back(kPolicyOem);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_PUBLIC) != 0) {
-    policies.emplace_back(kPolicyPublic);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_PRODUCT_PARTITION) != 0) {
-    policies.emplace_back(kPolicyProduct);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_SIGNATURE) != 0) {
-    policies.emplace_back(kPolicySignature);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_SYSTEM_PARTITION) != 0) {
-    policies.emplace_back(kPolicySystem);
-  }
-
-  if ((bitmask & PolicyFlags::POLICY_VENDOR_PARTITION) != 0) {
-    policies.emplace_back(kPolicyVendor);
-  }
-
-  return policies;
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp
new file mode 100644
index 0000000..fc5182a
--- /dev/null
+++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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 "include/idmap2/PolicyUtils.h"
+
+#include <sstream>
+
+#include "android-base/strings.h"
+#include "idmap2/Policies.h"
+
+using android::idmap2::policy::kPolicyStringToFlag;
+
+namespace android::idmap2::utils {
+
+Result<PolicyBitmask> PoliciesToBitmaskResult(const std::vector<std::string>& policies) {
+  std::vector<std::string> unknown_policies;
+  PolicyBitmask bitmask = 0;
+  for (const std::string& policy : policies) {
+    const auto result = std::find_if(kPolicyStringToFlag.begin(), kPolicyStringToFlag.end(),
+                                     [policy](const auto& it) { return policy == it.first; });
+    if (result != kPolicyStringToFlag.end()) {
+      bitmask |= result->second;
+    } else {
+      unknown_policies.emplace_back(policy.empty() ? "empty" : policy);
+    }
+  }
+
+  if (unknown_policies.empty()) {
+    return Result<PolicyBitmask>(bitmask);
+  }
+
+  auto prefix = unknown_policies.size() == 1 ? "policy" : "policies";
+  return Error("unknown %s: \"%s\"", prefix, android::base::Join(unknown_policies, ",").c_str());
+}
+
+std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) {
+  std::vector<std::string> policies;
+
+  for (const auto& policy : kPolicyStringToFlag) {
+    if ((bitmask & policy.second) != 0) {
+      policies.emplace_back(policy.first.to_string());
+    }
+  }
+
+  return policies;
+}
+
+}  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index fbf2c77..63ee8a6 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -16,6 +16,7 @@
 
 #include "idmap2/PrettyPrintVisitor.h"
 
+#include <istream>
 #include <string>
 
 #include "android-base/macros.h"
@@ -28,42 +29,59 @@
 
 #define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
 
+#define TAB "    "
+
 void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
 }
 
 void PrettyPrintVisitor::visit(const IdmapHeader& header) {
-  stream_ << "target apk path  : " << header.GetTargetPath() << std::endl
-          << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+  stream_ << "Paths:" << std::endl
+          << TAB "target apk path  : " << header.GetTargetPath() << std::endl
+          << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
+  const std::string& debug = header.GetDebugInfo();
+  if (!debug.empty()) {
+    std::istringstream debug_stream(debug);
+    std::string line;
+    stream_ << "Debug info:" << std::endl;
+    while (std::getline(debug_stream, line)) {
+      stream_ << TAB << line << std::endl;
+    }
+  }
 
   target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
   if (target_apk_) {
     target_am_.SetApkAssets({target_apk_.get()});
   }
-}
-
-void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+  stream_ << "Mapping:" << std::endl;
 }
 
 void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
-  last_seen_package_id_ = header.GetTargetPackageId();
 }
 
-void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& type_entry) {
+void PrettyPrintVisitor::visit(const IdmapData& data) {
   const bool target_package_loaded = !target_am_.GetApkAssets().empty();
-  for (uint16_t i = 0; i < type_entry.GetEntryCount(); i++) {
-    const EntryId entry = type_entry.GetEntry(i);
-    if (entry == kNoEntry) {
-      continue;
+  const ResStringPool string_pool(data.GetStringPoolData(),
+                                  data.GetHeader()->GetStringPoolLength());
+  const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
+
+  for (auto& target_entry : data.GetTargetEntries()) {
+    stream_ << TAB << base::StringPrintf("0x%08x ->", target_entry.target_id);
+
+    if (target_entry.data_type != Res_value::TYPE_REFERENCE &&
+        target_entry.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) {
+      stream_ << " " << utils::DataTypeToString(target_entry.data_type);
     }
 
-    const ResourceId target_resid =
-        RESID(last_seen_package_id_, type_entry.GetTargetTypeId(), type_entry.GetEntryOffset() + i);
-    const ResourceId overlay_resid =
-        RESID(last_seen_package_id_, type_entry.GetOverlayTypeId(), entry);
+    if (target_entry.data_type == Res_value::TYPE_STRING) {
+      stream_ << " \""
+              << string_pool.string8ObjectAt(target_entry.data_value - string_pool_offset).c_str()
+              << "\"";
+    } else {
+      stream_ << " " << base::StringPrintf("0x%08x", target_entry.data_value);
+    }
 
-    stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid);
     if (target_package_loaded) {
-      Result<std::string> name = utils::ResToTypeEntryName(target_am_, target_resid);
+      Result<std::string> name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
       if (name) {
         stream_ << " " << *name;
       }
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index dd14fd4..3f62a2a 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -16,22 +16,31 @@
 
 #include "idmap2/RawPrintVisitor.h"
 
+#include <algorithm>
 #include <cstdarg>
 #include <string>
 
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/ApkAssets.h"
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
 using android::ApkAssets;
+using android::idmap2::policy::PoliciesToDebugString;
+
+namespace {
+
+size_t StringSizeWhenEncoded(const std::string& s) {
+  size_t null_bytes = 4 - (s.size() % 4);
+  return sizeof(uint32_t) + s.size() + null_bytes;
+}
+
+}  // namespace
 
 namespace android::idmap2 {
 
-// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils
-#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
-
 void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
 }
 
@@ -40,53 +49,105 @@
   print(header.GetVersion(), "version");
   print(header.GetTargetCrc(), "target crc");
   print(header.GetOverlayCrc(), "overlay crc");
-  print(header.GetTargetPath().to_string(), "target path");
-  print(header.GetOverlayPath().to_string(), "overlay path");
+  print(header.GetFulfilledPolicies(), "fulfilled policies: %s",
+        PoliciesToDebugString(header.GetFulfilledPolicies()).c_str());
+  print(static_cast<uint8_t>(header.GetEnforceOverlayable()), "enforce overlayable");
+  print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path");
+  print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path");
+  print("...", StringSizeWhenEncoded(header.GetDebugInfo()), "debug info");
 
   target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
   if (target_apk_) {
     target_am_.SetApkAssets({target_apk_.get()});
   }
+
+  overlay_apk_ = ApkAssets::Load(header.GetOverlayPath().to_string());
+  if (overlay_apk_) {
+    overlay_am_.SetApkAssets({overlay_apk_.get()});
+  }
 }
 
 void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+  const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
+
+  for (auto& target_entry : data.GetTargetEntries()) {
+    Result<std::string> target_name(Error(""));
+    if (target_package_loaded) {
+      target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+    }
+    if (target_name) {
+      print(target_entry.target_id, "target id: %s", target_name->c_str());
+    } else {
+      print(target_entry.target_id, "target id");
+    }
+
+    print(target_entry.data_type, "type: %s",
+          utils::DataTypeToString(target_entry.data_type).data());
+
+    Result<std::string> overlay_name(Error(""));
+    if (overlay_package_loaded && (target_entry.data_type == Res_value::TYPE_REFERENCE ||
+                                   target_entry.data_type == Res_value::TYPE_DYNAMIC_REFERENCE)) {
+      overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.data_value);
+    }
+    if (overlay_name) {
+      print(target_entry.data_value, "value: %s", overlay_name->c_str());
+    } else {
+      print(target_entry.data_value, "value");
+    }
+  }
+
+  for (auto& overlay_entry : data.GetOverlayEntries()) {
+    Result<std::string> overlay_name(Error(""));
+    if (overlay_package_loaded) {
+      overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id);
+    }
+
+    if (overlay_name) {
+      print(overlay_entry.overlay_id, "overlay id: %s", overlay_name->c_str());
+    } else {
+      print(overlay_entry.overlay_id, "overlay id");
+    }
+
+    Result<std::string> target_name(Error(""));
+    if (target_package_loaded) {
+      target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id);
+    }
+
+    if (target_name) {
+      print(overlay_entry.target_id, "target id: %s", target_name->c_str());
+    } else {
+      print(overlay_entry.target_id, "target id");
+    }
+  }
+
+  const size_t string_pool_length = data.GetHeader()->GetStringPoolLength();
+  if (string_pool_length > 0) {
+    print_raw(string_pool_length, "%zu raw string pool bytes", string_pool_length);
+  }
 }
 
 void RawPrintVisitor::visit(const IdmapData::Header& header) {
-  print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id");
-  print(header.GetTypeCount(), "type count");
-  last_seen_package_id_ = header.GetTargetPackageId();
+  print(header.GetTargetPackageId(), "target package id");
+  print(header.GetOverlayPackageId(), "overlay package id");
+  print(header.GetTargetEntryCount(), "target entry count");
+  print(header.GetOverlayEntryCount(), "overlay entry count");
+  print(header.GetStringPoolIndexOffset(), "string pool index offset");
+  print(header.GetStringPoolLength(), "string pool byte length");
 }
 
-void RawPrintVisitor::visit(const IdmapData::TypeEntry& type_entry) {
-  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+// NOLINTNEXTLINE(cert-dcl50-cpp)
+void RawPrintVisitor::print(uint8_t value, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  std::string comment;
+  base::StringAppendV(&comment, fmt, ap);
+  va_end(ap);
 
-  print(static_cast<uint16_t>(type_entry.GetTargetTypeId()), "target type");
-  print(static_cast<uint16_t>(type_entry.GetOverlayTypeId()), "overlay type");
-  print(static_cast<uint16_t>(type_entry.GetEntryCount()), "entry count");
-  print(static_cast<uint16_t>(type_entry.GetEntryOffset()), "entry offset");
+  stream_ << base::StringPrintf("%08zx:       %02x", offset_, value) << "  " << comment
+          << std::endl;
 
-  for (uint16_t i = 0; i < type_entry.GetEntryCount(); i++) {
-    const EntryId entry = type_entry.GetEntry(i);
-    if (entry == kNoEntry) {
-      print(kPadding, "no entry");
-    } else {
-      const ResourceId target_resid = RESID(last_seen_package_id_, type_entry.GetTargetTypeId(),
-                                            type_entry.GetEntryOffset() + i);
-      const ResourceId overlay_resid =
-          RESID(last_seen_package_id_, type_entry.GetOverlayTypeId(), entry);
-      Result<std::string> name(Error(""));
-      if (target_package_loaded) {
-        name = utils::ResToTypeEntryName(target_am_, target_resid);
-      }
-      if (name) {
-        print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid,
-              name->c_str());
-      } else {
-        print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid);
-      }
-    }
-  }
+  offset_ += sizeof(uint8_t);
 }
 
 // NOLINTNEXTLINE(cert-dcl50-cpp)
@@ -116,17 +177,30 @@
 }
 
 // NOLINTNEXTLINE(cert-dcl50-cpp)
-void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) {
+void RawPrintVisitor::print(const std::string& value, size_t encoded_size, const char* fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   std::string comment;
   base::StringAppendV(&comment, fmt, ap);
   va_end(ap);
 
-  stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value
+  stream_ << base::StringPrintf("%08zx: ", offset_) << "........  " << comment << ": " << value
           << std::endl;
 
-  offset_ += kIdmapStringLength;
+  offset_ += encoded_size;
+}
+
+// NOLINTNEXTLINE(cert-dcl50-cpp)
+void RawPrintVisitor::print_raw(uint32_t length, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  std::string comment;
+  base::StringAppendV(&comment, fmt, ap);
+  va_end(ap);
+
+  stream_ << base::StringPrintf("%08zx: ", offset_) << "........  " << comment << std::endl;
+
+  offset_ += length;
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
new file mode 100644
index 0000000..34589a1
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2019 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 "idmap2/ResourceMapping.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
+#include "idmap2/PolicyUtils.h"
+#include "idmap2/ResourceUtils.h"
+
+using android::base::StringPrintf;
+using android::idmap2::utils::BitmaskToPolicies;
+using android::idmap2::utils::IsReference;
+using android::idmap2::utils::ResToTypeEntryName;
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
+namespace android::idmap2 {
+
+namespace {
+
+#define REWRITE_PACKAGE(resid, package_id) \
+  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
+#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
+
+std::string ConcatPolicies(const std::vector<std::string>& policies) {
+  std::string message;
+  for (const std::string& policy : policies) {
+    if (!message.empty()) {
+      message.append("|");
+    }
+    message.append(policy);
+  }
+
+  return message;
+}
+
+Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
+                              const OverlayManifestInfo& overlay_info,
+                              const PolicyBitmask& fulfilled_policies,
+                              const ResourceId& target_resource) {
+  static constexpr const PolicyBitmask sDefaultPolicies =
+      PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
+      PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE;
+
+  // If the resource does not have an overlayable definition, allow the resource to be overlaid if
+  // the overlay is preinstalled or signed with the same signature as the target.
+  if (!target_package.DefinesOverlayable()) {
+    return (sDefaultPolicies & fulfilled_policies) != 0
+               ? Result<Unit>({})
+               : Error(
+                     "overlay must be preinstalled or signed with the same signature as the "
+                     "target");
+  }
+
+  const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
+  if (overlayable_info == nullptr) {
+    // Do not allow non-overlayable resources to be overlaid.
+    return Error("target resource has no overlayable declaration");
+  }
+
+  if (overlay_info.target_name != overlayable_info->name) {
+    // If the overlay supplies a target overlayable name, the resource must belong to the
+    // overlayable defined with the specified name to be overlaid.
+    return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
+                 overlay_info.target_name.c_str(), overlayable_info->name.c_str());
+  }
+
+  // Enforce policy restrictions if the resource is declared as overlayable.
+  if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
+    return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
+                 ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
+                 ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
+  }
+
+  return Result<Unit>({});
+}
+
+// TODO(martenkongstad): scan for package name instead of assuming package at index 0
+//
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
+  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
+  if (packages.empty()) {
+    return nullptr;
+  }
+  int id = packages[0]->GetPackageId();
+  return loaded_arsc.GetPackageById(id);
+}
+
+Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
+                                                        const AssetManager2& asset_manager) {
+  Res_value value{};
+  ResTable_config selected_config{};
+  uint32_t flags;
+  auto cookie =
+      asset_manager.GetResource(resource_id, /* may_be_bag */ false,
+                                /* density_override */ 0U, &value, &selected_config, &flags);
+  if (cookie == kInvalidCookie) {
+    return Error("failed to find resource for id 0x%08x", resource_id);
+  }
+
+  if (value.dataType != Res_value::TYPE_STRING) {
+    return Error("resource for is 0x%08x is not a file", resource_id);
+  }
+
+  auto string_pool = asset_manager.GetStringPoolForCookie(cookie);
+  size_t len;
+  auto file_path16 = string_pool->stringAt(value.data, &len);
+  if (file_path16 == nullptr) {
+    return Error("failed to find string for index %d", value.data);
+  }
+
+  // Load the overlay resource mappings from the file specified using android:resourcesMap.
+  auto file_path = String8(String16(file_path16));
+  auto asset = asset_manager.OpenNonAsset(file_path.c_str(), Asset::AccessMode::ACCESS_BUFFER);
+  if (asset == nullptr) {
+    return Error("file \"%s\" not found", file_path.c_str());
+  }
+
+  return asset;
+}
+
+}  // namespace
+
+Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
+                                                               const LoadedPackage* target_package,
+                                                               const LoadedPackage* overlay_package,
+                                                               size_t string_pool_offset,
+                                                               const XmlParser& overlay_parser,
+                                                               LogInfo& log_info) {
+  ResourceMapping resource_mapping;
+  auto root_it = overlay_parser.tree_iterator();
+  if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
+    return Error("root element is not <overlay> tag");
+  }
+
+  const uint8_t target_package_id = target_package->GetPackageId();
+  const uint8_t overlay_package_id = overlay_package->GetPackageId();
+  auto overlay_it_end = root_it.end();
+  for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
+    if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
+      return Error("failed to parse overlay xml document");
+    }
+
+    if (overlay_it->event() != XmlParser::Event::START_TAG) {
+      continue;
+    }
+
+    if (overlay_it->name() != "item") {
+      return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+    }
+
+    Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
+    if (!target_resource) {
+      return Error(R"(<item> tag missing expected attribute "target")");
+    }
+
+    Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
+    if (!overlay_resource) {
+      return Error(R"(<item> tag missing expected attribute "value")");
+    }
+
+    ResourceId target_id =
+        target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
+    if (target_id == 0U) {
+      log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
+                                    << "\" in target resources");
+      continue;
+    }
+
+    // Retrieve the compile-time resource id of the target resource.
+    target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
+    if (overlay_resource->dataType == Res_value::TYPE_STRING) {
+      overlay_resource->data += string_pool_offset;
+    }
+
+    // Only rewrite resources defined within the overlay package to their corresponding target
+    // resource ids at runtime.
+    bool rewrite_overlay_reference =
+        IsReference(overlay_resource->dataType)
+            ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data)
+            : false;
+
+    if (rewrite_overlay_reference) {
+      overlay_resource->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
+    }
+
+    resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data,
+                                rewrite_overlay_reference);
+  }
+
+  return resource_mapping;
+}
+
+Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
+    const AssetManager2* target_am, const AssetManager2* overlay_am,
+    const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
+  ResourceMapping resource_mapping;
+  const uint8_t target_package_id = target_package->GetPackageId();
+  const auto end = overlay_package->end();
+  for (auto iter = overlay_package->begin(); iter != end; ++iter) {
+    const ResourceId overlay_resid = *iter;
+    Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
+    if (!name) {
+      continue;
+    }
+
+    // Find the resource with the same type and entry name within the target package.
+    const std::string full_name =
+        base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
+    ResourceId target_resource = target_am->GetResourceId(full_name);
+    if (target_resource == 0U) {
+      continue;
+    }
+
+    // Retrieve the compile-time resource id of the target resource.
+    target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
+    resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
+                                /* rewrite_overlay_reference */ false);
+  }
+
+  return resource_mapping;
+}
+
+void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
+                                                 const LoadedPackage* target_package,
+                                                 const LoadedPackage* overlay_package,
+                                                 const OverlayManifestInfo& overlay_info,
+                                                 const PolicyBitmask& fulfilled_policies,
+                                                 LogInfo& log_info) {
+  std::set<ResourceId> remove_ids;
+  for (const auto& target_map : target_map_) {
+    const ResourceId target_resid = target_map.first;
+    Result<Unit> success =
+        CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
+    if (success) {
+      continue;
+    }
+
+    // Attempting to overlay a resource that is not allowed to be overlaid is treated as a
+    // warning.
+    Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
+    if (!name) {
+      name = StringPrintf("0x%08x", target_resid);
+    }
+
+    log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
+                                  << "\" is not allowed to overlay resource \"" << *name
+                                  << "\" in target: " << success.GetErrorMessage());
+
+    remove_ids.insert(target_resid);
+  }
+
+  for (const ResourceId target_resid : remove_ids) {
+    RemoveMapping(target_resid);
+  }
+}
+
+Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
+                                                       const ApkAssets& overlay_apk_assets,
+                                                       const OverlayManifestInfo& overlay_info,
+                                                       const PolicyBitmask& fulfilled_policies,
+                                                       bool enforce_overlayable,
+                                                       LogInfo& log_info) {
+  AssetManager2 target_asset_manager;
+  if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */,
+                                         false /* filter_incompatible_configs*/)) {
+    return Error("failed to create target asset manager");
+  }
+
+  AssetManager2 overlay_asset_manager;
+  if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true /* invalidate_caches */,
+                                          false /* filter_incompatible_configs */)) {
+    return Error("failed to create overlay asset manager");
+  }
+
+  const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
+  if (target_arsc == nullptr) {
+    return Error("failed to load target resources.arsc");
+  }
+
+  const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
+  if (overlay_arsc == nullptr) {
+    return Error("failed to load overlay resources.arsc");
+  }
+
+  const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
+  if (target_pkg == nullptr) {
+    return Error("failed to load target package from resources.arsc");
+  }
+
+  const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
+  if (overlay_pkg == nullptr) {
+    return Error("failed to load overlay package from resources.arsc");
+  }
+
+  size_t string_pool_data_length = 0U;
+  size_t string_pool_offset = 0U;
+  std::unique_ptr<uint8_t[]> string_pool_data;
+  Result<ResourceMapping> resource_mapping = {{}};
+  if (overlay_info.resource_mapping != 0U) {
+    // Use the dynamic reference table to find the assigned resource id of the map xml.
+    const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
+    uint32_t resource_mapping_id = overlay_info.resource_mapping;
+    ref_table->lookupResourceId(&resource_mapping_id);
+
+    // Load the overlay resource mappings from the file specified using android:resourcesMap.
+    auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
+    if (!asset) {
+      return Error("failed opening xml for android:resourcesMap: %s",
+                   asset.GetErrorMessage().c_str());
+    }
+
+    auto parser =
+        XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
+    if (!parser) {
+      return Error("failed opening ResXMLTree");
+    }
+
+    // Copy the xml string pool data before the parse goes out of scope.
+    auto& string_pool = (*parser)->get_strings();
+    string_pool_data_length = string_pool.bytes();
+    string_pool_data.reset(new uint8_t[string_pool_data_length]);
+    memcpy(string_pool_data.get(), string_pool.data(), string_pool_data_length);
+
+    // Offset string indices by the size of the overlay resource table string pool.
+    string_pool_offset = overlay_arsc->GetStringPool()->size();
+
+    resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
+                                             string_pool_offset, *(*parser), log_info);
+  } else {
+    // If no file is specified using android:resourcesMap, it is assumed that the overlay only
+    // defines resources intended to override target resources of the same type and name.
+    resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
+                                                   target_pkg, overlay_pkg);
+  }
+
+  if (!resource_mapping) {
+    return resource_mapping.GetError();
+  }
+
+  if (enforce_overlayable) {
+    // Filter out resources the overlay is not allowed to override.
+    (*resource_mapping)
+        .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
+                                    fulfilled_policies, log_info);
+  }
+
+  resource_mapping->target_package_id_ = target_pkg->GetPackageId();
+  resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
+  resource_mapping->string_pool_offset_ = string_pool_offset;
+  resource_mapping->string_pool_data_ = std::move(string_pool_data);
+  resource_mapping->string_pool_data_length_ = string_pool_data_length;
+  return std::move(*resource_mapping);
+}
+
+OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
+  // An overlay resource can override multiple target resources at once. Rewrite the overlay
+  // resource as the first target resource it overrides.
+  OverlayResourceMap map;
+  for (const auto& mappings : overlay_map_) {
+    map.insert(std::make_pair(mappings.first, mappings.second));
+  }
+  return map;
+}
+
+Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
+                                         TargetValue::DataType data_type,
+                                         TargetValue::DataValue data_value,
+                                         bool rewrite_overlay_reference) {
+  if (target_map_.find(target_resource) != target_map_.end()) {
+    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+  }
+
+  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
+  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
+
+  target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
+
+  if (rewrite_overlay_reference && IsReference(data_type)) {
+    overlay_map_.insert(std::make_pair(data_value, target_resource));
+  }
+
+  return Result<Unit>({});
+}
+
+void ResourceMapping::RemoveMapping(ResourceId target_resource) {
+  auto target_iter = target_map_.find(target_resource);
+  if (target_iter == target_map_.end()) {
+    return;
+  }
+
+  const TargetValue value = target_iter->second;
+  target_map_.erase(target_iter);
+
+  if (!IsReference(value.data_type)) {
+    return;
+  }
+
+  auto overlay_iter = overlay_map_.equal_range(value.data_value);
+  for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
+    if (i->second == target_resource) {
+      overlay_map_.erase(i);
+      return;
+    }
+  }
+}
+
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index dce83e3..98d026b 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -22,18 +22,56 @@
 #include "androidfw/StringPiece.h"
 #include "androidfw/Util.h"
 #include "idmap2/Result.h"
-#include "idmap2/Xml.h"
+#include "idmap2/XmlParser.h"
 #include "idmap2/ZipFile.h"
 
 using android::StringPiece16;
 using android::idmap2::Result;
-using android::idmap2::Xml;
+using android::idmap2::XmlParser;
 using android::idmap2::ZipFile;
 using android::util::Utf16ToUtf8;
 
 namespace android::idmap2::utils {
 
-Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid) {
+bool IsReference(uint8_t data_type) {
+  return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
+}
+
+StringPiece DataTypeToString(uint8_t data_type) {
+  switch (data_type) {
+    case Res_value::TYPE_NULL:
+      return "null";
+    case Res_value::TYPE_REFERENCE:
+      return "reference";
+    case Res_value::TYPE_ATTRIBUTE:
+      return "attribute";
+    case Res_value::TYPE_STRING:
+      return "string";
+    case Res_value::TYPE_FLOAT:
+      return "float";
+    case Res_value::TYPE_DIMENSION:
+      return "dimension";
+    case Res_value::TYPE_FRACTION:
+      return "fraction";
+    case Res_value::TYPE_DYNAMIC_REFERENCE:
+      return "reference (dynamic)";
+    case Res_value::TYPE_DYNAMIC_ATTRIBUTE:
+      return "attribute (dynamic)";
+    case Res_value::TYPE_INT_DEC:
+    case Res_value::TYPE_INT_HEX:
+      return "integer";
+    case Res_value::TYPE_INT_BOOLEAN:
+      return "boolean";
+    case Res_value::TYPE_INT_COLOR_ARGB8:
+    case Res_value::TYPE_INT_COLOR_RGB8:
+    case Res_value::TYPE_INT_COLOR_RGB4:
+      return "color";
+    default:
+      return "unknown";
+  }
+}
+
+Result<std::string> ResToTypeEntryName(const AssetManager2& am, uint32_t resid) {
   AssetManager2::ResourceName name;
   if (!am.GetResourceName(resid, &name)) {
     return Error("no resource 0x%08x in asset manager", resid);
@@ -65,52 +103,72 @@
     return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
   }
 
-  std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size);
+  Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
   if (!xml) {
     return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
   }
 
+  auto manifest_it = (*xml)->tree_iterator();
+  if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
+    return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
+  }
+
+  auto overlay_it = std::find_if(manifest_it.begin(), manifest_it.end(), [](const auto& it) {
+    return it.event() == XmlParser::Event::START_TAG && it.name() == "overlay";
+  });
+
   OverlayManifestInfo info{};
-  const auto tag = xml->FindTag("overlay");
-  if (!tag) {
-    if (assert_overlay) {
-      return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str());
+  if (overlay_it == manifest_it.end()) {
+    if (!assert_overlay) {
+      return info;
     }
-    return info;
+    return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str());
   }
 
-  auto iter = tag->find("targetPackage");
-  if (iter == tag->end()) {
-    if (assert_overlay) {
-      return Error("android:targetPackage missing from <overlay> of %s", path.c_str());
-    }
+  if (auto result_str = overlay_it->GetAttributeStringValue("targetPackage")) {
+    info.target_package = *result_str;
   } else {
-    info.target_package = iter->second;
+    return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
+                 result_str.GetErrorMessage().c_str());
   }
 
-  iter = tag->find("targetName");
-  if (iter != tag->end()) {
-    info.target_name = iter->second;
+  if (auto result_str = overlay_it->GetAttributeStringValue("targetName")) {
+    info.target_name = *result_str;
   }
 
-  iter = tag->find("isStatic");
-  if (iter != tag->end()) {
-    info.is_static = std::stoul(iter->second) != 0U;
+  if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) {
+    if (IsReference((*result_value).dataType)) {
+      info.resource_mapping = (*result_value).data;
+    } else {
+      return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
+                   path.c_str());
+    }
   }
 
-  iter = tag->find("priority");
-  if (iter != tag->end()) {
-    info.priority = std::stoi(iter->second);
+  if (auto result_value = overlay_it->GetAttributeValue("isStatic")) {
+    if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
+        (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
+      info.is_static = (*result_value).data != 0U;
+    } else {
+      return Error("android:isStatic is not a boolean in AndroidManifest.xml of %s", path.c_str());
+    }
   }
 
-  iter = tag->find("requiredSystemPropertyName");
-  if (iter != tag->end()) {
-    info.requiredSystemPropertyName = iter->second;
+  if (auto result_value = overlay_it->GetAttributeValue("priority")) {
+    if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
+        (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
+      info.priority = (*result_value).data;
+    } else {
+      return Error("android:priority is not an integer in AndroidManifest.xml of %s", path.c_str());
+    }
   }
 
-  iter = tag->find("requiredSystemPropertyValue");
-  if (iter != tag->end()) {
-    info.requiredSystemPropertyValue = iter->second;
+  if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyName")) {
+    info.requiredSystemPropertyName = *result_str;
+  }
+
+  if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyValue")) {
+    info.requiredSystemPropertyValue = *result_str;
   }
 
   return info;
diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp
deleted file mode 100644
index 2645868..0000000
--- a/cmds/idmap2/libidmap2/Xml.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "idmap2/Xml.h"
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-
-namespace android::idmap2 {
-
-std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) {
-  std::unique_ptr<Xml> xml(new Xml());
-  if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) {
-    return nullptr;
-  }
-  return xml;
-}
-
-std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const {
-  const String16 tag_to_find(name.c_str(), name.size());
-  xml_.restart();
-  ResXMLParser::event_code_t type;
-  do {
-    type = xml_.next();
-    if (type == ResXMLParser::START_TAG) {
-      size_t len;
-      const String16 tag(xml_.getElementName(&len));
-      if (tag == tag_to_find) {
-        std::unique_ptr<std::map<std::string, std::string>> map(
-            new std::map<std::string, std::string>());
-        for (size_t i = 0; i < xml_.getAttributeCount(); i++) {
-          const String16 key16(xml_.getAttributeName(i, &len));
-          std::string key = String8(key16).c_str();
-
-          std::string value;
-          switch (xml_.getAttributeDataType(i)) {
-            case Res_value::TYPE_STRING: {
-              const String16 value16(xml_.getAttributeStringValue(i, &len));
-              value = String8(value16).c_str();
-            } break;
-            case Res_value::TYPE_INT_DEC:
-            case Res_value::TYPE_INT_HEX:
-            case Res_value::TYPE_INT_BOOLEAN: {
-              Res_value resValue;
-              xml_.getAttributeValue(i, &resValue);
-              value = std::to_string(resValue.data);
-            } break;
-            default:
-              return nullptr;
-          }
-
-          map->emplace(std::make_pair(key, value));
-        }
-        return map;
-      }
-    }
-  } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
-  return nullptr;
-}
-
-Xml::~Xml() {
-  xml_.uninit();
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
new file mode 100644
index 0000000..526a560
--- /dev/null
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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 "idmap2/XmlParser.h"
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace android::idmap2 {
+
+template <typename T>
+ResXMLParser::ResXMLPosition get_tree_position(const T& tree) {
+  ResXMLParser::ResXMLPosition pos{};
+  tree.getPosition(&pos);
+  return pos;
+}
+
+XmlParser::Node::Node(const ResXMLTree& tree) : Node(tree, get_tree_position(tree)) {
+}
+XmlParser::Node::Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos)
+    : parser_(tree) {
+  set_position(pos);
+}
+
+bool XmlParser::Node::operator==(const XmlParser::Node& rhs) const {
+  ResXMLParser::ResXMLPosition pos = get_position();
+  ResXMLParser::ResXMLPosition rhs_pos = rhs.get_position();
+  return pos.curExt == rhs_pos.curExt && pos.curNode == rhs_pos.curNode &&
+         pos.eventCode == rhs_pos.eventCode;
+}
+
+bool XmlParser::Node::operator!=(const XmlParser::Node& rhs) const {
+  return !(*this == rhs);
+}
+
+ResXMLParser::ResXMLPosition XmlParser::Node::get_position() const {
+  return get_tree_position(parser_);
+}
+
+void XmlParser::Node::set_position(const ResXMLParser::ResXMLPosition& pos) {
+  parser_.setPosition(pos);
+}
+
+bool XmlParser::Node::Seek(bool inner_child) {
+  if (parser_.getEventType() == XmlParser::Event::END_TAG) {
+    return false;
+  }
+
+  ssize_t depth = 0;
+  XmlParser::Event code;
+  while ((code = parser_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+         code != XmlParser::Event::END_DOCUMENT) {
+    if (code == XmlParser::Event::START_TAG) {
+      if (++depth == (inner_child ? 1 : 0)) {
+        return true;
+      }
+    } else if (code == XmlParser::Event::END_TAG) {
+      if (--depth == (inner_child ? -1 : -2)) {
+        return false;
+      }
+    }
+  }
+
+  return false;
+}
+
+XmlParser::Event XmlParser::Node::event() const {
+  return parser_.getEventType();
+}
+
+std::string XmlParser::Node::name() const {
+  size_t len;
+  const String16 key16(parser_.getElementName(&len));
+  return String8(key16).c_str();
+}
+
+Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const {
+  auto value = GetAttributeValue(name);
+  if (!value) {
+    return value.GetError();
+  }
+
+  switch ((*value).dataType) {
+    case Res_value::TYPE_STRING: {
+      size_t len;
+      const String16 value16(parser_.getStrings().stringAt((*value).data, &len));
+      return std::string(String8(value16).c_str());
+    }
+    case Res_value::TYPE_INT_DEC:
+    case Res_value::TYPE_INT_HEX:
+    case Res_value::TYPE_INT_BOOLEAN: {
+      return std::to_string((*value).data);
+    }
+    default:
+      return Error(R"(Failed to convert attribute "%s" value to a string)", name.c_str());
+  }
+}
+
+Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const {
+  size_t len;
+  for (size_t i = 0; i < parser_.getAttributeCount(); i++) {
+    const String16 key16(parser_.getAttributeName(i, &len));
+    std::string key = String8(key16).c_str();
+    if (key != name) {
+      continue;
+    }
+
+    Res_value res_value{};
+    if (parser_.getAttributeValue(i, &res_value) == BAD_TYPE) {
+      return Error(R"(Bad value for attribute "%s")", name.c_str());
+    }
+
+    return res_value;
+  }
+
+  return Error(R"(Failed to find attribute "%s")", name.c_str());
+}
+
+Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
+                                                           bool copy_data) {
+  auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
+  if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
+    return Error("Malformed xml block");
+  }
+
+  // Find the beginning of the first tag.
+  XmlParser::Event event;
+  while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+         event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
+  }
+
+  if (event == XmlParser::Event::END_DOCUMENT) {
+    return Error("Root tag was not be found");
+  }
+
+  if (event == XmlParser::Event::BAD_DOCUMENT) {
+    return Error("Bad xml document");
+  }
+
+  return parser;
+}
+
+XmlParser::~XmlParser() {
+  tree_.uninit();
+}
+
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
index 4f5e3a4..1e1a218 100644
--- a/cmds/idmap2/libidmap2/ZipFile.cpp
+++ b/cmds/idmap2/libidmap2/ZipFile.cpp
@@ -34,6 +34,7 @@
   ::ZipArchiveHandle handle;
   int32_t status = ::OpenArchive(path.c_str(), &handle);
   if (status != 0) {
+    ::CloseArchive(handle);
     return nullptr;
   }
   return std::unique_ptr<ZipFile>(new ZipFile(handle));
diff --git a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
new file mode 100644
index 0000000..5bd353a
--- /dev/null
+++ b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
+#define IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
+
+#include <array>
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+using android::base::StringPrintf;
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
+namespace android::idmap2::policy {
+
+constexpr const char* kPolicyActor = "actor";
+constexpr const char* kPolicyOdm = "odm";
+constexpr const char* kPolicyOem = "oem";
+constexpr const char* kPolicyProduct = "product";
+constexpr const char* kPolicyPublic = "public";
+constexpr const char* kPolicySignature = "signature";
+constexpr const char* kPolicySystem = "system";
+constexpr const char* kPolicyVendor = "vendor";
+
+inline static const std::array<std::pair<StringPiece, PolicyFlags>, 8> kPolicyStringToFlag = {
+    std::pair{kPolicyActor, PolicyFlags::ACTOR_SIGNATURE},
+    {kPolicyOdm, PolicyFlags::ODM_PARTITION},
+    {kPolicyOem, PolicyFlags::OEM_PARTITION},
+    {kPolicyProduct, PolicyFlags::PRODUCT_PARTITION},
+    {kPolicyPublic, PolicyFlags::PUBLIC},
+    {kPolicySignature, PolicyFlags::SIGNATURE},
+    {kPolicySystem, PolicyFlags::SYSTEM_PARTITION},
+    {kPolicyVendor, PolicyFlags::VENDOR_PARTITION},
+};
+
+inline static std::string PoliciesToDebugString(PolicyBitmask policies) {
+  std::string str;
+  uint32_t remaining = policies;
+  for (auto const& policy : kPolicyStringToFlag) {
+    if ((policies & policy.second) != policy.second) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.first.data());
+    remaining &= ~policy.second;
+  }
+  if (remaining != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", remaining));
+  }
+  return !str.empty() ? str : "none";
+}
+
+}  // namespace android::idmap2::policy
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
index 41d3c69..a372abd 100755
--- a/cmds/idmap2/static-checks.sh
+++ b/cmds/idmap2/static-checks.sh
@@ -27,10 +27,11 @@
     local red="\e[31m"
     local green="\e[32m"
     local reset="\e[0m"
+    local output
 
     _log "${green}[ RUN      ]${reset} ${label}"
-    local output="$(eval "$cmd")"
-    if [[ -z "${output}" ]]; then
+    output="$(eval "$cmd" 2>&1)"
+    if [[ $? -eq 0 ]]; then
         _log "${green}[       OK ]${reset} ${label}"
         return 0
     else
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index 9348ab7..5fea7bc 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -18,6 +18,7 @@
 #include <sstream>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "TestHelpers.h"
 #include "androidfw/ApkAssets.h"
@@ -47,118 +48,49 @@
   ASSERT_TRUE(result2);
   const auto idmap2 = std::move(*result2);
 
+  ASSERT_EQ(idmap1->GetHeader()->GetFulfilledPolicies(),
+            idmap2->GetHeader()->GetFulfilledPolicies());
+  ASSERT_EQ(idmap1->GetHeader()->GetEnforceOverlayable(),
+            idmap2->GetHeader()->GetEnforceOverlayable());
+  ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
   ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc());
   ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
   ASSERT_EQ(idmap1->GetData().size(), 1U);
   ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size());
 
-  const auto& data1 = idmap1->GetData()[0];
-  const auto& data2 = idmap2->GetData()[0];
+  const std::vector<std::unique_ptr<const IdmapData>>& data_blocks1 = idmap1->GetData();
+  ASSERT_EQ(data_blocks1.size(), 1U);
+  const std::unique_ptr<const IdmapData>& data1 = data_blocks1[0];
+  ASSERT_THAT(data1, NotNull());
 
-  ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId());
-  ASSERT_EQ(data1->GetTypeEntries().size(), 2U);
-  ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size());
-  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0));
-  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1));
-  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2));
-  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0));
-  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1));
-  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2));
-}
+  const std::vector<std::unique_ptr<const IdmapData>>& data_blocks2 = idmap2->GetData();
+  ASSERT_EQ(data_blocks2.size(), 1U);
+  const std::unique_ptr<const IdmapData>& data2 = data_blocks2[0];
+  ASSERT_THAT(data2, NotNull());
 
-TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) {
-  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  const auto& target_entries1 = data1->GetTargetEntries();
+  const auto& target_entries2 = data2->GetTargetEntries();
+  ASSERT_EQ(target_entries1.size(), target_entries2.size());
+  ASSERT_EQ(target_entries1[0].target_id, target_entries2[0].target_id);
+  ASSERT_EQ(target_entries1[0].data_value, target_entries2[0].data_value);
 
-  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  ASSERT_EQ(target_entries1[1].target_id, target_entries2[1].target_id);
+  ASSERT_EQ(target_entries1[1].data_value, target_entries2[1].data_value);
 
-  const auto idmap =
-      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
-                           PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
-  ASSERT_TRUE(idmap);
+  ASSERT_EQ(target_entries1[2].target_id, target_entries2[2].target_id);
+  ASSERT_EQ(target_entries1[2].data_value, target_entries2[2].data_value);
 
-  std::stringstream stream;
-  BinaryStreamVisitor visitor(stream);
-  (*idmap)->accept(&visitor);
-  const std::string str = stream.str();
-  const StringPiece data(str);
-  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data);
-  ASSERT_THAT(loaded_idmap, NotNull());
-  ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f);
+  const auto& overlay_entries1 = data1->GetOverlayEntries();
+  const auto& overlay_entries2 = data2->GetOverlayEntries();
+  ASSERT_EQ(overlay_entries1.size(), overlay_entries2.size());
+  ASSERT_EQ(overlay_entries1[0].overlay_id, overlay_entries2[0].overlay_id);
+  ASSERT_EQ(overlay_entries1[0].target_id, overlay_entries2[0].target_id);
 
-  const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01);
-  ASSERT_THAT(header, NotNull());
+  ASSERT_EQ(overlay_entries1[1].overlay_id, overlay_entries2[1].overlay_id);
+  ASSERT_EQ(overlay_entries1[1].target_id, overlay_entries2[1].target_id);
 
-  EntryId entry;
-  bool success = LoadedIdmap::Lookup(header, 0x0000, &entry);
-  ASSERT_TRUE(success);
-  ASSERT_EQ(entry, 0x0000);
-
-  header = loaded_idmap->GetEntryMapForType(0x02);
-  ASSERT_THAT(header, NotNull());
-
-  success = LoadedIdmap::Lookup(header, 0x0000, &entry);  // string/a
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0001, &entry);  // string/b
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0002, &entry);  // string/c
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0003, &entry);  // string/policy_odm
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0004, &entry);  // string/policy_oem
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0005, &entry);  // string/other
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0006, &entry);  // string/not_overlayable
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0007, &entry);  // string/policy_product
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0008, &entry);  // string/policy_public
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0009, &entry);  // string/policy_system
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x000a, &entry);  // string/policy_system_vendor
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x000b, &entry);  // string/policy_signature
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x000c, &entry);  // string/str1
-  ASSERT_TRUE(success);
-  ASSERT_EQ(entry, 0x0000);
-
-  success = LoadedIdmap::Lookup(header, 0x000d, &entry);  // string/str2
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x000e, &entry);  // string/str3
-  ASSERT_TRUE(success);
-  ASSERT_EQ(entry, 0x0001);
-
-  success = LoadedIdmap::Lookup(header, 0x000f, &entry);  // string/str4
-  ASSERT_TRUE(success);
-  ASSERT_EQ(entry, 0x0002);
-
-  success = LoadedIdmap::Lookup(header, 0x0010, &entry);  // string/x
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0011, &entry);  // string/y
-  ASSERT_FALSE(success);
-
-  success = LoadedIdmap::Lookup(header, 0x0012, &entry);  // string/z
-  ASSERT_FALSE(success);
+  ASSERT_EQ(overlay_entries1[2].overlay_id, overlay_entries2[2].overlay_id);
+  ASSERT_EQ(overlay_entries1[2].target_id, overlay_entries2[2].target_id);
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index f55acee..8af4037 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -56,12 +56,12 @@
     return type == DT_REG && path.size() > 4 && path.compare(path.size() - 4, 4, ".apk") == 0;
   });
   ASSERT_THAT(v, NotNull());
-  ASSERT_EQ(v->size(), 10U);
+  ASSERT_EQ(v->size(), 11U);
   ASSERT_EQ(std::set<std::string>(v->begin(), v->end()),
             std::set<std::string>(
                 {root + "/target/target.apk", root + "/target/target-no-overlayable.apk",
                  root + "/overlay/overlay.apk", root + "/overlay/overlay-no-name.apk",
-                 root + "/overlay/overlay-no-name-static.apk",
+                 root + "/overlay/overlay-no-name-static.apk", root + "/overlay/overlay-shared.apk",
                  root + "/overlay/overlay-static-1.apk", root + "/overlay/overlay-static-2.apk",
                  root + "/signature-overlay/signature-overlay.apk",
                  root + "/system-overlay/system-overlay.apk",
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index 499eb99..d896cf9 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -34,6 +34,7 @@
 #include <string>
 #include <vector>
 
+#include "R.h"
 #include "TestHelpers.h"
 #include "androidfw/PosixUtils.h"
 #include "gmock/gmock.h"
@@ -127,10 +128,14 @@
   // clang-format on
   ASSERT_THAT(result, NotNull());
   ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
-  ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos);
-  ASSERT_NE(result->stdout.find("0x7f02000c -> 0x7f020000 string/str1"), std::string::npos);
-  ASSERT_NE(result->stdout.find("0x7f02000e -> 0x7f020001 string/str3"), std::string::npos);
-  ASSERT_NE(result->stdout.find("0x7f02000f -> 0x7f020002 string/str4"), std::string::npos);
+  ASSERT_NE(result->stdout.find(R::target::integer::literal::int1 + " -> 0x7f010000 integer/int1"),
+            std::string::npos);
+  ASSERT_NE(result->stdout.find(R::target::string::literal::str1 + " -> 0x7f020000 string/str1"),
+            std::string::npos);
+  ASSERT_NE(result->stdout.find(R::target::string::literal::str3 + " -> 0x7f020001 string/str3"),
+            std::string::npos);
+  ASSERT_NE(result->stdout.find(R::target::string::literal::str4 + " -> 0x7f020002 string/str4"),
+            std::string::npos);
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -141,7 +146,6 @@
   ASSERT_THAT(result, NotNull());
   ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
   ASSERT_NE(result->stdout.find("00000000: 504d4449  magic"), std::string::npos);
-  ASSERT_NE(result->stdout.find("00000210:     007f  target package id"), std::string::npos);
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -298,7 +302,7 @@
                           "lookup",
                           "--idmap-path", GetIdmapPath(),
                           "--config", "",
-                          "--resid", "0x7f02000c"});  // string/str1
+                          "--resid", R::target::string::literal::str1});
   // clang-format on
   ASSERT_THAT(result, NotNull());
   ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 0f47f1e..6fab5e0 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -22,6 +22,8 @@
 #include <utility>
 #include <vector>
 
+#include "R.h"
+#include "TestConstants.h"
 #include "TestHelpers.h"
 #include "android-base/macros.h"
 #include "androidfw/ApkAssets.h"
@@ -30,12 +32,25 @@
 #include "idmap2/BinaryStreamVisitor.h"
 #include "idmap2/CommandLineOptions.h"
 #include "idmap2/Idmap.h"
+#include "idmap2/LogInfo.h"
 
+using android::Res_value;
 using ::testing::IsNull;
 using ::testing::NotNull;
 
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
 namespace android::idmap2 {
 
+#define ASSERT_TARGET_ENTRY(entry, target_resid, type, value) \
+  ASSERT_EQ(entry.target_id, target_resid);                   \
+  ASSERT_EQ(entry.data_type, type);                           \
+  ASSERT_EQ(entry.data_value, value)
+
+#define ASSERT_OVERLAY_ENTRY(entry, overlay_resid, target_resid) \
+  ASSERT_EQ(entry.overlay_id, overlay_resid);                    \
+  ASSERT_EQ(entry.target_id, target_resid)
+
 TEST(IdmapTests, TestCanonicalIdmapPathFor) {
   ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"),
             "/foo/vendor@overlay@bar.apk@idmap");
@@ -47,17 +62,20 @@
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
   ASSERT_EQ(header->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(header->GetVersion(), 0x01U);
+  ASSERT_EQ(header->GetVersion(), 0x04U);
   ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
-  ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk");
-  ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk");
+  ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
+  ASSERT_EQ(header->GetEnforceOverlayable(), true);
+  ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk");
+  ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk");
+  ASSERT_EQ(header->GetDebugInfo(), "debug");
 }
 
 TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
   // overwrite the target path string, including the terminating null, with '.'
-  for (size_t i = 0x10; i < 0x110; i++) {
+  for (size_t i = 0x15; i < 0x115; i++) {
     raw[i] = '.';
   }
   std::istringstream stream(raw);
@@ -66,58 +84,40 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
-  const size_t offset = 0x210;
+  const size_t offset = 0x221;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
                   idmap_raw_data_len - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
-  ASSERT_EQ(header->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(header->GetTypeCount(), 2U);
-}
-
-TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) {
-  const size_t offset = 0x214;
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  idmap_raw_data_len - offset);
-  std::istringstream stream(raw);
-
-  std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream);
-  ASSERT_THAT(data, NotNull());
-  ASSERT_EQ(data->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(data->GetOverlayTypeId(), 0x02U);
-  ASSERT_EQ(data->GetEntryCount(), 1U);
-  ASSERT_EQ(data->GetEntryOffset(), 0U);
-  ASSERT_EQ(data->GetEntry(0), 0U);
+  ASSERT_EQ(header->GetTargetEntryCount(), 0x03);
+  ASSERT_EQ(header->GetOverlayEntryCount(), 0x03);
 }
 
 TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
-  const size_t offset = 0x210;
+  const size_t offset = 0x221;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
                   idmap_raw_data_len - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
   ASSERT_THAT(data, NotNull());
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U);
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 2U);
 
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 1U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 0U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 3U);
+  ASSERT_TARGET_ENTRY(target_entries[0], 0x7f020000, 0x01 /* Res_value::TYPE_REFERENCE */,
+                      0x7f020000);
+  ASSERT_TARGET_ENTRY(target_entries[1], 0x7f030000, 0x01 /* Res_value::TYPE_REFERENCE */,
+                      0x7f030000);
+  ASSERT_TARGET_ENTRY(target_entries[2], 0x7f030002, 0x01 /* Res_value::TYPE_REFERENCE */,
+                      0x7f030001);
 
-  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03U);
-  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03U);
-  ASSERT_EQ(types[1]->GetEntryCount(), 3U);
-  ASSERT_EQ(types[1]->GetEntryOffset(), 3U);
-  ASSERT_EQ(types[1]->GetEntry(0), 0x0000U);
-  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
-  ASSERT_EQ(types[1]->GetEntry(2), 0x0001U);
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(target_entries.size(), 3U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x7f020000, 0x7f020000);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x7f030000, 0x7f030000);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x7f030001, 0x7f030002);
 }
 
 TEST(IdmapTests, CreateIdmapFromBinaryStream) {
@@ -130,34 +130,31 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
-  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk");
-  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk");
+  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11);
+  ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk");
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk");
 
   const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
   ASSERT_EQ(dataBlocks.size(), 1U);
 
   const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U);
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 2U);
+  ASSERT_THAT(data, NotNull());
 
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 1U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 0U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 3U);
+  ASSERT_TARGET_ENTRY(target_entries[0], 0x7f020000, Res_value::TYPE_REFERENCE, 0x7f020000);
+  ASSERT_TARGET_ENTRY(target_entries[1], 0x7f030000, Res_value::TYPE_REFERENCE, 0x7f030000);
+  ASSERT_TARGET_ENTRY(target_entries[2], 0x7f030002, Res_value::TYPE_REFERENCE, 0x7f030001);
 
-  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03U);
-  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03U);
-  ASSERT_EQ(types[1]->GetEntryCount(), 3U);
-  ASSERT_EQ(types[1]->GetEntryOffset(), 3U);
-  ASSERT_EQ(types[1]->GetEntry(0), 0x0000U);
-  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
-  ASSERT_EQ(types[1]->GetEntry(2), 0x0001U);
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(target_entries.size(), 3U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x7f020000, 0x7f020000);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x7f030000, 0x7f030000);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x7f030001, 0x7f030002);
 }
 
 TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
@@ -169,301 +166,192 @@
   ASSERT_FALSE(result);
 }
 
-void CreateIdmap(const StringPiece& target_apk_path, const StringPiece& overlay_apk_path,
-                 const PolicyBitmask& fulfilled_policies, bool enforce_overlayable,
-                 std::unique_ptr<const Idmap>* out_idmap) {
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path.to_string());
-  ASSERT_THAT(target_apk, NotNull());
-
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path.to_string());
-  ASSERT_THAT(overlay_apk, NotNull());
-
-  auto result =
-      Idmap::FromApkAssets(target_apk_path.to_string(), *target_apk, overlay_apk_path.to_string(),
-                           *overlay_apk, fulfilled_policies, enforce_overlayable);
-  *out_idmap = result ? std::move(*result) : nullptr;
-}
-
-TEST(IdmapTests, CreateIdmapFromApkAssets) {
-  std::unique_ptr<const Idmap> idmap;
+TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ true, &idmap);
+
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                           /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
+  ASSERT_THAT(idmap, NotNull());
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01U);
-  ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x76a20829);
-  ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x8635c2ed);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
+  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
+  ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
   ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
-  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
-
-  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
-  ASSERT_EQ(dataBlocks.size(), 1U);
-
-  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
-
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U);
-
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 2U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 1U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 0U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);
-
-  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02U);
-  ASSERT_EQ(types[1]->GetEntryCount(), 4U);
-  ASSERT_EQ(types[1]->GetEntryOffset(), 12U);
-  ASSERT_EQ(types[1]->GetEntry(0), 0x0000U);
-  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
-  ASSERT_EQ(types[1]->GetEntry(2), 0x0001U);
-  ASSERT_EQ(types[1]->GetEntry(3), 0x0002U);
 }
 
-// Overlays should abide by all overlayable restrictions if enforcement of overlayable is enabled.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
-  std::unique_ptr<const Idmap> idmap;
+Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
+    const android::StringPiece& local_target_apk_path,
+    const android::StringPiece& local_overlay_apk_path, const OverlayManifestInfo& overlay_info,
+    const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) {
+  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data());
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
+  }
+
+  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data());
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  if (!overlay_apk) {
+    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+  }
+
+  LogInfo log_info;
+  auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info,
+                                                fulfilled_policies, enforce_overlayable, log_info);
+
+  if (!mapping) {
+    return mapping.GetError();
+  }
+
+  return IdmapData::FromResourceMapping(*mapping);
+}
+
+TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
-  std::string overlay_apk_path = GetTestDataPath() + "/system-overlay/system-overlay.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path,
-              PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ true, &idmap);
+  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
+
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                           /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
 
   const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
   ASSERT_EQ(dataBlocks.size(), 1U);
 
   const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_THAT(data, NotNull());
 
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U);
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1,
+                      Res_value::TYPE_DYNAMIC_REFERENCE, R::overlay::integer::int1);
+  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay::string::str1);
+  ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay::string::str3);
+  ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay::string::str4);
 
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 1U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 4U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 8U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);   // string/policy_public
-  ASSERT_EQ(types[0]->GetEntry(1), kNoEntry);  // string/policy_signature
-  ASSERT_EQ(types[0]->GetEntry(2), 0x0001U);   // string/policy_system
-  ASSERT_EQ(types[0]->GetEntry(3), 0x0002U);   // string/policy_system_vendor
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay::integer::int1, R::target::integer::int1);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay::string::str1, R::target::string::str1);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay::string::str3, R::target::string::str3);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
 }
 
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySignature) {
-  std::unique_ptr<const Idmap> idmap;
+TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
-  std::string overlay_apk_path = GetTestDataPath() + "/signature-overlay/signature-overlay.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path,
-              PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_SIGNATURE,
-              /* enforce_overlayable */ true, &idmap);
+  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
+
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                           /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
 
   const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
   ASSERT_EQ(dataBlocks.size(), 1U);
 
   const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_THAT(data, NotNull());
 
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U);
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1,
+                      Res_value::TYPE_DYNAMIC_REFERENCE, R::overlay_shared::integer::int1);
+  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay_shared::string::str1);
+  ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay_shared::string::str3);
+  ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay_shared::string::str4);
 
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 1U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 1U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 9U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);  // string/policy_signature
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay_shared::integer::int1,
+                       R::target::integer::int1);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay_shared::string::str1,
+                       R::target::string::str1);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay_shared::string::str3,
+                       R::target::string::str3);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay_shared::string::str4,
+                       R::target::string::str4);
 }
 
-// Overlays should abide by all overlayable restrictions if enforcement of overlayable is enabled.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
-  std::unique_ptr<const Idmap> idmap;
-  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
-  std::string overlay_apk_path =
-      GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path,
-              PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
+TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0x7f030001;  // xml/overlays_different_packages
+  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info,
+                                               PolicyFlags::PUBLIC,
+                                               /* enforce_overlayable */ false);
 
-  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
-  ASSERT_EQ(dataBlocks.size(), 1U);
+  ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
+  auto& data = *idmap_data;
 
-  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 2U);
+  ASSERT_TARGET_ENTRY(target_entries[0], R::target::string::str1, Res_value::TYPE_REFERENCE,
+                      0x0104000a);  // -> android:string/ok
+  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE,
+                      R::overlay::string::str3);
 
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U);
-
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 1U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 4U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 8U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0005U);   // string/policy_public
-  ASSERT_EQ(types[0]->GetEntry(1), kNoEntry);  // string/policy_signature
-  ASSERT_EQ(types[0]->GetEntry(2), 0x0007U);   // string/policy_system
-  ASSERT_EQ(types[0]->GetEntry(3), 0x0008U);   // string/policy_system_vendor
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(overlay_entries.size(), 1U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay::string::str3, R::target::string::str3);
 }
 
-// Overlays should ignore all overlayable restrictions if enforcement of overlayable is disabled.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
-  std::unique_ptr<const Idmap> idmap;
-  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
-  std::string overlay_apk_path =
-      GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path,
-              PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ false, &idmap);
-  ASSERT_THAT(idmap, NotNull());
+TEST(IdmapTests, CreateIdmapDataInlineResources) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0x7f030002;  // xml/overlays_inline
+  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info,
+                                               PolicyFlags::PUBLIC,
+                                               /* enforce_overlayable */ false);
 
-  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
-  ASSERT_EQ(dataBlocks.size(), 1U);
+  ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
+  auto& data = *idmap_data;
 
-  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  constexpr size_t overlay_string_pool_size = 8U;
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 2U);
+  ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1, Res_value::TYPE_INT_DEC,
+                      73U);  // -> 73
+  ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_STRING,
+                      overlay_string_pool_size + 0U);  // -> "Hello World"
 
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U);
-
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 1U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 9U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 3U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);  // string/not_overlayable
-  ASSERT_EQ(types[0]->GetEntry(1), 0x0001U);  // string/policy_odm
-  ASSERT_EQ(types[0]->GetEntry(2), 0x0002U);  // string/policy_oem
-  ASSERT_EQ(types[0]->GetEntry(3), 0x0003U);  // string/other
-  ASSERT_EQ(types[0]->GetEntry(4), 0x0004U);  // string/policy_product
-  ASSERT_EQ(types[0]->GetEntry(5), 0x0005U);  // string/policy_public
-  ASSERT_EQ(types[0]->GetEntry(6), 0x0006U);  // string/policy_signature
-  ASSERT_EQ(types[0]->GetEntry(7), 0x0007U);  // string/policy_system
-  ASSERT_EQ(types[0]->GetEntry(8), 0x0008U);  // string/policy_system_vendor
-}
-
-// Overlays that do not specify a target <overlayable> can overlay resources defined as overlayable.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
-  std::unique_ptr<const Idmap> idmap;
-  std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk";
-  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-no-name.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ false, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-
-  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
-  ASSERT_EQ(dataBlocks.size(), 1U);
-
-  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
-
-  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U);
-
-  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-  ASSERT_EQ(types.size(), 2U);
-
-  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-  ASSERT_EQ(types[0]->GetEntryCount(), 1U);
-  ASSERT_EQ(types[0]->GetEntryOffset(), 0U);
-  ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);  // string/int1
-
-  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02U);
-  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02U);
-  ASSERT_EQ(types[1]->GetEntryCount(), 4U);
-  ASSERT_EQ(types[1]->GetEntryOffset(), 12U);
-  ASSERT_EQ(types[1]->GetEntry(0), 0x0000U);   // string/str1
-  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);  // string/str2
-  ASSERT_EQ(types[1]->GetEntry(2), 0x0001U);   // string/str3
-  ASSERT_EQ(types[1]->GetEntry(3), 0x0002U);   // string/str4
-}
-
-// Overlays that are not pre-installed and are not signed with the same signature as the target
-// cannot overlay packages that have not defined overlayable resources.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsDefaultPoliciesPublicFail) {
-  std::unique_ptr<const Idmap> idmap;
-  std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk";
-  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-no-name.apk";
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, IsNull());
-}
-
-// Overlays that are pre-installed or are signed with the same signature as the target can overlay
-// packages that have not defined overlayable resources.
-TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsDefaultPolicies) {
-  std::unique_ptr<const Idmap> idmap;
-  std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk";
-  std::string overlay_apk_path =
-      GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk";
-
-  auto CheckEntries = [&]() -> void {
-    const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
-    ASSERT_EQ(dataBlocks.size(), 1U);
-
-    const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
-    ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU);
-    ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U);
-
-    const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
-    ASSERT_EQ(types.size(), 1U);
-
-    ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U);
-    ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U);
-    ASSERT_EQ(types[0]->GetEntryCount(), 9U);
-    ASSERT_EQ(types[0]->GetEntryOffset(), 3U);
-    ASSERT_EQ(types[0]->GetEntry(0), 0x0000U);  // string/not_overlayable
-    ASSERT_EQ(types[0]->GetEntry(1), 0x0001U);  // string/policy_odm
-    ASSERT_EQ(types[0]->GetEntry(2), 0x0002U);  // string/policy_oem
-    ASSERT_EQ(types[0]->GetEntry(3), 0x0003U);  // string/other
-    ASSERT_EQ(types[0]->GetEntry(4), 0x0004U);  // string/policy_product
-    ASSERT_EQ(types[0]->GetEntry(5), 0x0005U);  // string/policy_public
-    ASSERT_EQ(types[0]->GetEntry(6), 0x0006U);  // string/policy_signature
-    ASSERT_EQ(types[0]->GetEntry(7), 0x0007U);  // string/policy_system
-    ASSERT_EQ(types[0]->GetEntry(8), 0x0008U);  // string/policy_system_vendor
-  };
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_SIGNATURE,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PRODUCT_PARTITION,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_SYSTEM_PARTITION,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_VENDOR_PARTITION,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_ODM_PARTITION,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
-
-  CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_OEM_PARTITION,
-              /* enforce_overlayable */ true, &idmap);
-  ASSERT_THAT(idmap, NotNull());
-  CheckEntries();
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(overlay_entries.size(), 0U);
 }
 
 TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) {
@@ -480,9 +368,8 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  const auto result =
-      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
-                           PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+  const auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                           /* enforce_overlayable */ true);
   ASSERT_FALSE(result);
 }
 
@@ -497,8 +384,7 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  auto result = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
-                                     PolicyFlags::POLICY_PUBLIC,
+  auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
                                      /* enforce_overlayable */ true);
   ASSERT_TRUE(result);
   const auto idmap = std::move(*result);
@@ -509,7 +395,8 @@
 
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
-  ASSERT_TRUE(header->IsUpToDate());
+  ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                 PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // magic: bytes (0x0, 0x03)
   std::string bad_magic_string(stream.str());
@@ -522,7 +409,8 @@
       IdmapHeader::FromBinaryStream(bad_magic_stream);
   ASSERT_THAT(bad_magic_header, NotNull());
   ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // version: bytes (0x4, 0x07)
   std::string bad_version_string(stream.str());
@@ -535,7 +423,8 @@
       IdmapHeader::FromBinaryStream(bad_version_stream);
   ASSERT_THAT(bad_version_header, NotNull());
   ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion());
-  ASSERT_FALSE(bad_version_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // target crc: bytes (0x8, 0xb)
   std::string bad_target_crc_string(stream.str());
@@ -548,7 +437,8 @@
       IdmapHeader::FromBinaryStream(bad_target_crc_stream);
   ASSERT_THAT(bad_target_crc_header, NotNull());
   ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
-  ASSERT_FALSE(bad_target_crc_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // overlay crc: bytes (0xc, 0xf)
   std::string bad_overlay_crc_string(stream.str());
@@ -561,27 +451,55 @@
       IdmapHeader::FromBinaryStream(bad_overlay_crc_stream);
   ASSERT_THAT(bad_overlay_crc_header, NotNull());
   ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
-  ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
-  // target path: bytes (0x10, 0x10f)
+  // fulfilled policy: bytes (0x10, 0x13)
+  std::string bad_policy_string(stream.str());
+  bad_policy_string[0x10] = '.';
+  bad_policy_string[0x11] = '.';
+  bad_policy_string[0x12] = '.';
+  bad_policy_string[0x13] = '.';
+  std::stringstream bad_policy_stream(bad_policy_string);
+  std::unique_ptr<const IdmapHeader> bad_policy_header =
+      IdmapHeader::FromBinaryStream(bad_policy_stream);
+  ASSERT_THAT(bad_policy_header, NotNull());
+  ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies());
+  ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                             PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+
+  // enforce overlayable: bytes (0x14)
+  std::string bad_enforce_string(stream.str());
+  bad_enforce_string[0x14] = '\0';
+  std::stringstream bad_enforce_stream(bad_enforce_string);
+  std::unique_ptr<const IdmapHeader> bad_enforce_header =
+      IdmapHeader::FromBinaryStream(bad_enforce_stream);
+  ASSERT_THAT(bad_enforce_header, NotNull());
+  ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable());
+  ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                              PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+
+  // target path: bytes (0x15, 0x114)
   std::string bad_target_path_string(stream.str());
-  bad_target_path_string[0x10] = '\0';
+  bad_target_path_string[0x15] = '\0';
   std::stringstream bad_target_path_stream(bad_target_path_string);
   std::unique_ptr<const IdmapHeader> bad_target_path_header =
       IdmapHeader::FromBinaryStream(bad_target_path_stream);
   ASSERT_THAT(bad_target_path_header, NotNull());
   ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
-  ASSERT_FALSE(bad_target_path_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
-  // overlay path: bytes (0x110, 0x20f)
+  // overlay path: bytes (0x115, 0x214)
   std::string bad_overlay_path_string(stream.str());
-  bad_overlay_path_string[0x110] = '\0';
+  bad_overlay_path_string[0x115] = '\0';
   std::stringstream bad_overlay_path_stream(bad_overlay_path_string);
   std::unique_ptr<const IdmapHeader> bad_overlay_path_header =
       IdmapHeader::FromBinaryStream(bad_overlay_path_stream);
   ASSERT_THAT(bad_overlay_path_header, NotNull());
   ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath());
-  ASSERT_FALSE(bad_overlay_path_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 }
 
 class TestVisitor : public Visitor {
@@ -605,10 +523,6 @@
     stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl;
   }
 
-  void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) override {
-    stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl;
-  }
-
  private:
   std::ostream& stream_;
 };
@@ -625,12 +539,10 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_EQ(test_stream.str(),
-            "TestVisitor::visit(Idmap)\n"
             "TestVisitor::visit(IdmapHeader)\n"
-            "TestVisitor::visit(IdmapData)\n"
+            "TestVisitor::visit(Idmap)\n"
             "TestVisitor::visit(IdmapData::Header)\n"
-            "TestVisitor::visit(IdmapData::TypeEntry)\n"
-            "TestVisitor::visit(IdmapData::TypeEntry)\n");
+            "TestVisitor::visit(IdmapData)\n");
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/PoliciesTests.cpp b/cmds/idmap2/tests/PoliciesTests.cpp
index eca7404..1b27759 100644
--- a/cmds/idmap2/tests/PoliciesTests.cpp
+++ b/cmds/idmap2/tests/PoliciesTests.cpp
@@ -17,76 +17,96 @@
 #include <string>
 
 #include "TestHelpers.h"
+#include "androidfw/ResourceTypes.h"
 #include "gtest/gtest.h"
-#include "idmap2/Policies.h"
+#include "idmap2/PolicyUtils.h"
 
-using android::idmap2::PolicyBitmask;
-using android::idmap2::PolicyFlags;
+using android::idmap2::utils::BitmaskToPolicies;
+using android::idmap2::utils::PoliciesToBitmaskResult;
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
-TEST(PoliciesTests, PoliciesToBitmasks) {
-  const auto bitmask1 = PoliciesToBitmask({"system"});
+TEST(PoliciesTests, PoliciesToBitmaskResults) {
+  const auto bitmask1 = PoliciesToBitmaskResult({"system"});
   ASSERT_TRUE(bitmask1);
-  ASSERT_EQ(*bitmask1, PolicyFlags::POLICY_SYSTEM_PARTITION);
+  ASSERT_EQ(*bitmask1, PolicyFlags::SYSTEM_PARTITION);
 
-  const auto bitmask2 = PoliciesToBitmask({"system", "vendor"});
+  const auto bitmask2 = PoliciesToBitmaskResult({"system", "vendor"});
   ASSERT_TRUE(bitmask2);
-  ASSERT_EQ(*bitmask2, PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION);
+  ASSERT_EQ(*bitmask2, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION);
 
-  const auto bitmask3 = PoliciesToBitmask({"vendor", "system"});
+  const auto bitmask3 = PoliciesToBitmaskResult({"vendor", "system"});
   ASSERT_TRUE(bitmask3);
-  ASSERT_EQ(*bitmask3, PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION);
+  ASSERT_EQ(*bitmask3, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION);
 
-  const auto bitmask4 = PoliciesToBitmask({"odm", "oem", "public", "product", "system", "vendor"});
+  const auto bitmask4 =
+      PoliciesToBitmaskResult({"odm", "oem", "public", "product", "system", "vendor"});
   ASSERT_TRUE(bitmask4);
-  ASSERT_EQ(*bitmask4, PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION |
-                           PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_PRODUCT_PARTITION |
-                           PolicyFlags::POLICY_SYSTEM_PARTITION |
-                           PolicyFlags::POLICY_VENDOR_PARTITION);
+  ASSERT_EQ(*bitmask4, PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION |
+                           PolicyFlags::PUBLIC | PolicyFlags::PRODUCT_PARTITION |
+                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION);
 
-  const auto bitmask5 = PoliciesToBitmask({"system", "system", "system"});
+  const auto bitmask5 = PoliciesToBitmaskResult({"system", "system", "system"});
   ASSERT_TRUE(bitmask5);
-  ASSERT_EQ(*bitmask5, PolicyFlags::POLICY_SYSTEM_PARTITION);
+  ASSERT_EQ(*bitmask5, PolicyFlags::SYSTEM_PARTITION);
 
-  const auto bitmask6 = PoliciesToBitmask({""});
+  const auto bitmask6 = PoliciesToBitmaskResult({""});
   ASSERT_FALSE(bitmask6);
 
-  const auto bitmask7 = PoliciesToBitmask({"foo"});
+  const auto bitmask7 = PoliciesToBitmaskResult({"foo"});
   ASSERT_FALSE(bitmask7);
 
-  const auto bitmask8 = PoliciesToBitmask({"system", "foo"});
+  const auto bitmask8 = PoliciesToBitmaskResult({"system", "foo"});
   ASSERT_FALSE(bitmask8);
 
-  const auto bitmask9 = PoliciesToBitmask({"system", ""});
+  const auto bitmask9 = PoliciesToBitmaskResult({"system", ""});
   ASSERT_FALSE(bitmask9);
 
-  const auto bitmask10 = PoliciesToBitmask({"system "});
+  const auto bitmask10 = PoliciesToBitmaskResult({"system "});
   ASSERT_FALSE(bitmask10);
+
+  const auto bitmask11 = PoliciesToBitmaskResult({"signature"});
+  ASSERT_TRUE(bitmask11);
+  ASSERT_EQ(*bitmask11, PolicyFlags::SIGNATURE);
+
+  const auto bitmask12 = PoliciesToBitmaskResult({"actor"});
+  ASSERT_TRUE(bitmask12);
+  ASSERT_EQ(*bitmask12, PolicyFlags::ACTOR_SIGNATURE);
 }
 
 TEST(PoliciesTests, BitmaskToPolicies) {
-  const auto policies1 = BitmaskToPolicies(PolicyFlags::POLICY_PUBLIC);
+  const auto policies1 = BitmaskToPolicies(PolicyFlags::PUBLIC);
   ASSERT_EQ(1, policies1.size());
   ASSERT_EQ(policies1[0], "public");
 
-  const auto policies2 = BitmaskToPolicies(PolicyFlags::POLICY_SYSTEM_PARTITION |
-                                           PolicyFlags::POLICY_VENDOR_PARTITION);
+  const auto policies2 =
+      BitmaskToPolicies(PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION);
   ASSERT_EQ(2, policies2.size());
   ASSERT_EQ(policies2[0], "system");
   ASSERT_EQ(policies2[1], "vendor");
 
-  const auto policies3 = BitmaskToPolicies(
-      PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION |
-      PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_PRODUCT_PARTITION |
-      PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION);
+  const auto policies3 =
+      BitmaskToPolicies(PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION |
+                        PolicyFlags::PUBLIC | PolicyFlags::PRODUCT_PARTITION |
+                        PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION);
   ASSERT_EQ(2, policies2.size());
   ASSERT_EQ(policies3[0], "odm");
   ASSERT_EQ(policies3[1], "oem");
-  ASSERT_EQ(policies3[2], "public");
-  ASSERT_EQ(policies3[3], "product");
+  ASSERT_EQ(policies3[2], "product");
+  ASSERT_EQ(policies3[3], "public");
   ASSERT_EQ(policies3[4], "system");
   ASSERT_EQ(policies3[5], "vendor");
+
+  const auto policies4 = BitmaskToPolicies(PolicyFlags::SIGNATURE);
+  ASSERT_EQ(1, policies4.size());
+  ASSERT_EQ(policies4[0], "signature");
+
+  const auto policies5 = BitmaskToPolicies(PolicyFlags::ACTOR_SIGNATURE);
+  ASSERT_EQ(1, policies5.size());
+  ASSERT_EQ(policies5[0], "actor");
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index c412504..9a10079 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -18,19 +18,22 @@
 #include <sstream>
 #include <string>
 
+#include "R.h"
 #include "TestHelpers.h"
 #include "androidfw/ApkAssets.h"
 #include "androidfw/Idmap.h"
+#include "androidfw/ResourceTypes.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "idmap2/Idmap.h"
-#include "idmap2/Policies.h"
 #include "idmap2/PrettyPrintVisitor.h"
 
 using ::testing::NotNull;
 
 using android::ApkAssets;
-using android::idmap2::PolicyBitmask;
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
@@ -43,9 +46,8 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  const auto idmap =
-      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
-                           PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
@@ -54,7 +56,8 @@
 
   ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
   ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
-  ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos);
+  ASSERT_NE(stream.str().find(R::target::integer::literal::int1 + " -> 0x7f010000 integer/int1\n"),
+            std::string::npos);
 }
 
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
new file mode 100644
index 0000000..aed263a
--- /dev/null
+++ b/cmds/idmap2/tests/R.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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 IDMAP2_TESTS_R_H
+#define IDMAP2_TESTS_R_H
+
+#include <idmap2/ResourceUtils.h>
+
+namespace android::idmap2 {
+
+static std::string hexify(ResourceId id) {
+  std::stringstream stream;
+  stream << std::hex << static_cast<uint32_t>(id);
+  return stream.str();
+}
+
+// clang-format off
+namespace R::target {
+  namespace integer {
+    constexpr ResourceId int1 = 0x7f010000;
+
+    namespace literal {
+      inline const std::string int1 = hexify(R::target::integer::int1);
+    }
+  }
+
+  namespace string {
+    constexpr ResourceId not_overlayable = 0x7f020003;
+    constexpr ResourceId other = 0x7f020004;
+    constexpr ResourceId policy_actor = 0x7f020005;
+    constexpr ResourceId policy_odm = 0x7f020006;
+    constexpr ResourceId policy_oem = 0x7f020007;
+    constexpr ResourceId policy_product = 0x7f020008;
+    constexpr ResourceId policy_public = 0x7f020009;
+    constexpr ResourceId policy_signature = 0x7f02000a;
+    constexpr ResourceId policy_system = 0x7f02000b;
+    constexpr ResourceId policy_system_vendor = 0x7f02000c;
+    constexpr ResourceId str1 = 0x7f02000d;
+    constexpr ResourceId str3 = 0x7f02000f;
+    constexpr ResourceId str4 = 0x7f020010;
+
+    namespace literal {
+      inline const std::string str1 = hexify(R::target::string::str1);
+      inline const std::string str3 = hexify(R::target::string::str3);
+      inline const std::string str4 = hexify(R::target::string::str4);
+    }
+  }
+}
+
+namespace R::overlay {
+  namespace integer {
+    constexpr ResourceId int1 = 0x7f010000;
+  }
+  namespace string {
+    constexpr ResourceId str1 = 0x7f020000;
+    constexpr ResourceId str3 = 0x7f020001;
+    constexpr ResourceId str4 = 0x7f020002;
+  }
+}
+
+namespace R::overlay_shared {
+  namespace integer {
+    constexpr ResourceId int1 = 0x00010000;
+  }
+  namespace string {
+    constexpr ResourceId str1 = 0x00020000;
+    constexpr ResourceId str3 = 0x00020001;
+    constexpr ResourceId str4 = 0x00020002;
+  }
+}
+
+namespace R::system_overlay::string {
+  constexpr ResourceId policy_public = 0x7f010000;
+  constexpr ResourceId policy_system = 0x7f010001;
+  constexpr ResourceId policy_system_vendor = 0x7f010002;
+}
+
+namespace R::system_overlay_invalid::string {
+  constexpr ResourceId not_overlayable = 0x7f010000;
+  constexpr ResourceId other = 0x7f010001;
+  constexpr ResourceId policy_actor = 0x7f010002;
+  constexpr ResourceId policy_odm = 0x7f010003;
+  constexpr ResourceId policy_oem = 0x7f010004;
+  constexpr ResourceId policy_product = 0x7f010005;
+  constexpr ResourceId policy_public = 0x7f010006;
+  constexpr ResourceId policy_signature = 0x7f010007;
+  constexpr ResourceId policy_system = 0x7f010008;
+  constexpr ResourceId policy_system_vendor = 0x7f010009;
+};
+// clang-format on
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_TESTS_R_H
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 2695176..b268d5a 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -16,20 +16,38 @@
 
 #include <cstdio>  // fclose
 #include <memory>
+#include <regex>
 #include <sstream>
 #include <string>
 
+#include "TestConstants.h"
 #include "TestHelpers.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "idmap2/Idmap.h"
 #include "idmap2/RawPrintVisitor.h"
 
+using android::base::StringPrintf;
 using ::testing::NotNull;
 
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
 namespace android::idmap2 {
 
+#define ASSERT_CONTAINS_REGEX(pattern, str)                       \
+  do {                                                            \
+    ASSERT_TRUE(std::regex_search(str, std::regex(pattern)))      \
+        << "pattern '" << pattern << "' not found in\n--------\n" \
+        << str << "--------";                                     \
+  } while (0)
+
+#define ADDRESS "[0-9a-f]{8}: "
+
 TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
+  fclose(stderr);  // silence expected warnings
+
   const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
   std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
   ASSERT_THAT(target_apk, NotNull());
@@ -38,21 +56,36 @@
   std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
   ASSERT_THAT(overlay_apk, NotNull());
 
-  const auto idmap =
-      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
-                           PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
   RawPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("00000000: 504d4449  magic\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000004: 00000001  version\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000008: 76a20829  target crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000000c: 8635c2ed  overlay crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000021c: 00000000  0x7f010000 -> 0x7f010000 integer/int1\n"),
-            std::string::npos);
+  ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(
+      StringPrintf(ADDRESS "%s  target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
+      stream.str());
+  ASSERT_CONTAINS_REGEX(
+      StringPrintf(ADDRESS "%s  overlay crc\n", android::idmap2::TestConstants::OVERLAY_CRC_STRING),
+      stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000001  fulfilled policies: public\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  enforce overlayable\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  string pool index offset\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool byte length\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      07  type: reference \\(dynamic\\)\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  value: integer/int1\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1\n", stream.str());
 }
 
 TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
@@ -68,11 +101,23 @@
   RawPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("00000000: 504d4449  magic\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000004: 00000001  version\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000008: 00001234  target crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000000c: 00005678  overlay crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000021c: 00000000  0x7f020000 -> 0x7f020000\n"), std::string::npos);
+  ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00001234  target crc\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00005678  overlay crc\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000011  fulfilled policies: public|signature\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  enforce overlayable\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000003  target entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000003  overlay entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000000  string pool index offset\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000000  string pool byte length\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  type: reference\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  value\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  overlay id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id\n", stream.str());
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
new file mode 100644
index 0000000..de039f4
--- /dev/null
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>  // fclose
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "R.h"
+#include "TestHelpers.h"
+#include "androidfw/ResourceTypes.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "idmap2/LogInfo.h"
+#include "idmap2/ResourceMapping.h"
+
+using android::Res_value;
+using android::idmap2::utils::ExtractOverlayManifestInfo;
+
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
+namespace android::idmap2 {
+
+#define ASSERT_RESULT(r)                             \
+  do {                                               \
+    auto result = r;                                 \
+    ASSERT_TRUE(result) << result.GetErrorMessage(); \
+  } while (0)
+
+Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
+                                               const android::StringPiece& local_overlay_apk_path,
+                                               const OverlayManifestInfo& overlay_info,
+                                               const PolicyBitmask& fulfilled_policies,
+                                               bool enforce_overlayable) {
+  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data());
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
+  }
+
+  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data());
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  if (!overlay_apk) {
+    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+  }
+
+  LogInfo log_info;
+  return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies,
+                                        enforce_overlayable, log_info);
+}
+
+Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
+                                               const android::StringPiece& local_overlay_apk_path,
+                                               const PolicyBitmask& fulfilled_policies,
+                                               bool enforce_overlayable) {
+  auto overlay_info = ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path.data());
+  if (!overlay_info) {
+    return overlay_info.GetError();
+  }
+  return TestGetResourceMapping(local_target_apk_path, local_overlay_apk_path, *overlay_info,
+                                fulfilled_policies, enforce_overlayable);
+}
+
+Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& target_resource,
+                           const uint8_t type, const uint32_t value, bool rewrite) {
+  auto target_map = mapping.GetTargetToOverlayMap();
+  auto entry_map = target_map.find(target_resource);
+  if (entry_map == target_map.end()) {
+    return Error("Failed to find mapping for target resource");
+  }
+
+  if (entry_map->second.data_type != type) {
+    return Error(R"(Expected type: "0x%02x" Actual type: "0x%02x")", type,
+                 entry_map->second.data_type);
+  }
+
+  if (entry_map->second.data_value != value) {
+    return Error(R"(Expected value: "0x%08x" Actual value: "0x%08x")", type,
+                 entry_map->second.data_value);
+  }
+
+  auto overlay_map = mapping.GetOverlayToTargetMap();
+  auto overlay_iter = overlay_map.find(entry_map->second.data_value);
+  if ((overlay_iter != overlay_map.end()) != rewrite) {
+    return Error(R"(Expected rewriting: "%s")", rewrite ? "true" : "false");
+  }
+
+  return Result<Unit>({});
+}
+
+TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0U;  // no xml
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+                                          PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
+  ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_REFERENCE,
+                              R::overlay::integer::int1, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str1, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str3, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str4, false /* rewrite */));
+}
+
+TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0x7f030003;  // xml/overlays_swap
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+                                          PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE,
+                              R::overlay::string::str4, true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE,
+                              R::overlay::string::str1, true /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE,
+                              R::overlay::string::str3, true /* rewrite */));
+}
+
+TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0x7f030001;  // xml/overlays_different_packages
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+                                          PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+  ASSERT_EQ(res.GetOverlayToTargetMap().size(), 1U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x0104000a,
+                              false /* rewrite */));  // -> android:string/ok
+  ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE,
+                              0x7f020001, true /* rewrite */));
+}
+
+TEST(ResourceMappingTests, InlineResources) {
+  OverlayManifestInfo info{};
+  info.target_package = "test.target";
+  info.target_name = "TestResources";
+  info.resource_mapping = 0x7f030002;  // xml/overlays_inline
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+                                          PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  constexpr size_t overlay_string_pool_size = 8U;
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+  ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_STRING,
+                              overlay_string_pool_size + 0U,
+                              false /* rewrite */));  // -> "Hello World"
+  ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U,
+                              false /* rewrite */));  // -> 73
+}
+
+TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
+  auto resources =
+      TestGetResourceMapping("/target/target.apk", "/system-overlay/system-overlay.apk",
+                             PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
+                             /* enforce_overlayable */ true);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
+                              R::system_overlay::string::policy_public, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE,
+                              R::system_overlay::string::policy_system, false /* rewrite */));
+  ASSERT_RESULT(
+      MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE,
+                    R::system_overlay::string::policy_system_vendor, false /* rewrite */));
+}
+
+// Resources that are not declared as overlayable and resources that a protected by policies the
+// overlay does not fulfill must not map to overlay resources.
+TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
+  auto resources = TestGetResourceMapping("/target/target.apk",
+                                          "/system-overlay-invalid/system-overlay-invalid.apk",
+                                          PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_public,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_system,
+                              false /* rewrite */));
+  ASSERT_RESULT(
+      MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE,
+                    R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */));
+}
+
+// Resources that are not declared as overlayable and resources that a protected by policies the
+// overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
+// off.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
+  auto resources = TestGetResourceMapping("/target/target.apk",
+                                          "/system-overlay-invalid/system-overlay-invalid.apk",
+                                          PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 10U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::not_overlayable,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::other, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_actor,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_odm, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_oem, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_product,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_public,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_signature,
+                              false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_system,
+                              false /* rewrite */));
+  ASSERT_RESULT(
+      MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE,
+                    R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */));
+}
+
+// Overlays that do not target an <overlayable> tag can overlay resources defined within any
+// <overlayable> tag.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
+  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-no-name.apk",
+                                          PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
+  ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_REFERENCE,
+                              R::overlay::integer::int1, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str1, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str3, false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_REFERENCE,
+                              R::overlay::string::str4, false /* rewrite */));
+}
+
+// Overlays that are neither pre-installed nor signed with the same signature as the target cannot
+// overlay packages that have not defined overlayable resources.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
+  auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
+                                          "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
+}
+
+// Overlays that are pre-installed or are signed with the same signature as the target can overlay
+// packages that have not defined overlayable resources.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
+  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
+    auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
+                                            "/system-overlay-invalid/system-overlay-invalid.apk",
+                                            fulfilled_policies,
+                                            /* enforce_overlayable */ true);
+
+    ASSERT_TRUE(resources) << resources.GetErrorMessage();
+    auto& res = *resources;
+    ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U);
+    ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::not_overlayable,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::other, false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_actor,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_odm,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_oem,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_product,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_public,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_signature,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_system,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(
+        res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE,
+        R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */));
+  };
+
+  CheckEntries(PolicyFlags::SIGNATURE);
+  CheckEntries(PolicyFlags::PRODUCT_PARTITION);
+  CheckEntries(PolicyFlags::SYSTEM_PARTITION);
+  CheckEntries(PolicyFlags::VENDOR_PARTITION);
+  CheckEntries(PolicyFlags::ODM_PARTITION);
+  CheckEntries(PolicyFlags::OEM_PARTITION);
+}
+
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
new file mode 100644
index 0000000..6bc924e
--- /dev/null
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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 IDMAP2_TESTS_TESTCONSTANTS_H
+#define IDMAP2_TESTS_TESTCONSTANTS_H
+
+namespace android::idmap2::TestConstants {
+
+constexpr const auto TARGET_CRC = 0x41c60c8c;
+constexpr const auto TARGET_CRC_STRING = "41c60c8c";
+
+constexpr const auto OVERLAY_CRC = 0xc054fb26;
+constexpr const auto OVERLAY_CRC_STRING = "c054fb26";
+
+}  // namespace android::idmap2::TestConstants
+
+#endif  // IDMAP2_TESTS_TESTCONSTANTS_H
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index adea329..b599dcb 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -30,7 +30,7 @@
     0x49, 0x44, 0x4d, 0x50,
 
     // 0x4: version
-    0x01, 0x00, 0x00, 0x00,
+    0x04, 0x00, 0x00, 0x00,
 
     // 0x8: target crc
     0x34, 0x12, 0x00, 0x00,
@@ -38,8 +38,14 @@
     // 0xc: overlay crc
     0x78, 0x56, 0x00, 0x00,
 
-    // 0x10: target path "target.apk"
-    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // 0x10: fulfilled policies
+    0x11, 0x00, 0x00, 0x00,
+
+    // 0x14: enforce overlayable
+    0x01,
+
+    // 0x15: target path "targetX.apk"
+    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -56,8 +62,8 @@
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-    // 0x110: overlay path "overlay.apk"
-    0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // 0x115: overlay path "overlayX.apk"
+    0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -74,56 +80,77 @@
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
+    // 0x215: debug string
+    // string length, including terminating null
+    0x08, 0x00, 0x00, 0x00,
+
+    // string contents "debug\0\0\0" (padded to word alignment)
+    0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
+
     // DATA HEADER
-    // 0x210: target package id
-    0x7f, 0x00,
+    // 0x221: target_package_id
+    0x7f,
 
-    // 0x212: types count
-    0x02, 0x00,
+    // 0x222: overlay_package_id
+    0x7f,
 
-    // DATA BLOCK
-    // 0x214: target type
-    0x02, 0x00,
+    // 0x223: target_entry_count
+    0x03, 0x00, 0x00, 0x00,
 
-    // 0x216: overlay type
-    0x02, 0x00,
+    // 0x227: overlay_entry_count
+    0x03, 0x00, 0x00, 0x00,
 
-    // 0x218: entry count
-    0x01, 0x00,
-
-    // 0x21a: entry offset
-    0x00, 0x00,
-
-    // 0x21c: entries
+    // 0x22b: string_pool_offset
     0x00, 0x00, 0x00, 0x00,
 
-    // DATA BLOCK
-    // 0x220: target type
-    0x03, 0x00,
-
-    // 0x222: overlay type
-    0x03, 0x00,
-
-    // 0x224: entry count
-    0x03, 0x00,
-
-    // 0x226: entry offset
-    0x03, 0x00,
-
-    // 0x228, 0x22c, 0x230: entries
+    // 0x22f: string_pool_byte_length
     0x00, 0x00, 0x00, 0x00,
 
-    0xff, 0xff, 0xff, 0xff,
+    // TARGET ENTRIES
+    // 0x233: 0x7f020000
+    0x00, 0x00, 0x02, 0x7f,
 
-    0x01, 0x00, 0x00, 0x00};
+    // 0x237: TYPE_REFERENCE
+    0x01,
 
-const unsigned int idmap_raw_data_len = 565;
+    // 0x238: 0x7f020000
+    0x00, 0x00, 0x02, 0x7f,
+
+    // 0x23c: 0x7f030000
+    0x00, 0x00, 0x03, 0x7f,
+
+    // 0x240: TYPE_REFERENCE
+    0x01,
+
+    // 0x241: 0x7f030000
+    0x00, 0x00, 0x03, 0x7f,
+
+    // 0x245: 0x7f030002
+    0x02, 0x00, 0x03, 0x7f,
+
+    // 0x249: TYPE_REFERENCE
+    0x01,
+
+    // 0x24a: 0x7f030001
+    0x01, 0x00, 0x03, 0x7f,
+
+    // OVERLAY ENTRIES
+    // 0x24e: 0x7f020000 -> 0x7f020000
+    0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
+
+    // 0x256: 0x7f030000 -> 0x7f030000
+    0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
+
+    // 0x25e: 0x7f030001 -> 0x7f030002
+    0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f};
+
+const unsigned int idmap_raw_data_len = 0x266;
 
 std::string GetTestDataPath();
 
 class Idmap2Tests : public testing::Test {
  protected:
-  virtual void SetUp() {
+  void SetUp() override {
 #ifdef __ANDROID__
     tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX";
 #else
@@ -136,7 +163,7 @@
     idmap_path_ = tmp_dir_path_ + "/a.idmap";
   }
 
-  virtual void TearDown() {
+  void TearDown() override {
     EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0)
         << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno);
   }
diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp
new file mode 100644
index 0000000..1a7eaca
--- /dev/null
+++ b/cmds/idmap2/tests/XmlParserTests.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2019 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 <cstdio>  // fclose
+#include <memory>
+#include <string>
+
+#include "TestHelpers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "idmap2/XmlParser.h"
+#include "idmap2/ZipFile.h"
+
+namespace android::idmap2 {
+
+Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  if (zip == nullptr) {
+    return Error("Failed to open zip file");
+  }
+
+  auto data = zip->Uncompress(test_file);
+  if (data == nullptr) {
+    return Error("Failed to open xml file");
+  }
+
+  return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
+}
+
+TEST(XmlParserTests, Create) {
+  auto xml = CreateTestParser("AndroidManifest.xml");
+  ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+  fclose(stderr);  // silence expected warnings from libandroidfw
+  const char* not_xml = "foo";
+  auto fail = XmlParser::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
+  ASSERT_FALSE(fail);
+}
+
+TEST(XmlParserTests, NextChild) {
+  auto xml = CreateTestParser("res/xml/test.xml");
+  ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+  auto root_iter = (*xml)->tree_iterator();
+  ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
+  ASSERT_EQ(root_iter->name(), "a");
+
+  auto a_iter = root_iter.begin();
+  ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG);
+  ASSERT_EQ(a_iter->name(), "b");
+
+  auto c_iter = a_iter.begin();
+  ASSERT_EQ(c_iter->event(), XmlParser::Event::START_TAG);
+  ASSERT_EQ(c_iter->name(), "c");
+
+  ++c_iter;
+  ASSERT_EQ(c_iter->event(), XmlParser::Event::END_TAG);
+  ASSERT_EQ(c_iter, a_iter.end());
+
+  ++a_iter;
+  ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG);
+  ASSERT_EQ(a_iter->name(), "d");
+
+  // Skip the <e> tag.
+  ++a_iter;
+  ASSERT_EQ(a_iter->event(), XmlParser::Event::END_TAG);
+  ASSERT_EQ(a_iter, root_iter.end());
+}
+
+TEST(XmlParserTests, AttributeValues) {
+  auto xml = CreateTestParser("res/xml/test.xml");
+  ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+  // Start at the <a> tag.
+  auto root_iter = (*xml)->tree_iterator();
+
+  // Start at the <b> tag.
+  auto a_iter = root_iter.begin();
+  auto attribute_str = a_iter->GetAttributeStringValue("type_string");
+  ASSERT_TRUE(attribute_str);
+  ASSERT_EQ(*attribute_str, "fortytwo");
+
+  auto attribute_value = a_iter->GetAttributeValue("type_int_dec");
+  ASSERT_TRUE(attribute_value);
+  ASSERT_EQ(attribute_value->data, 42);
+
+  attribute_value = a_iter->GetAttributeValue("type_int_hex");
+  ASSERT_TRUE(attribute_value);
+  ASSERT_EQ(attribute_value->data, 42);
+
+  attribute_value = a_iter->GetAttributeValue("type_int_boolean");
+  ASSERT_TRUE(attribute_value);
+  ASSERT_EQ(attribute_value->data, 0xffffffff);
+}
+
+TEST(XmlParserTests, IteratorEquality) {
+  auto xml = CreateTestParser("res/xml/test.xml");
+  ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+  // Start at the <a> tag.
+  auto root_iter_1 = (*xml)->tree_iterator();
+  auto root_iter_2 = (*xml)->tree_iterator();
+  ASSERT_EQ(root_iter_1, root_iter_2);
+  ASSERT_EQ(*root_iter_1, *root_iter_2);
+
+  // Start at the <b> tag.
+  auto a_iter_1 = root_iter_1.begin();
+  auto a_iter_2 = root_iter_2.begin();
+  ASSERT_NE(a_iter_1, root_iter_1.end());
+  ASSERT_NE(a_iter_2, root_iter_2.end());
+  ASSERT_EQ(a_iter_1, a_iter_2);
+  ASSERT_EQ(*a_iter_1, *a_iter_2);
+
+  // Move to the <d> tag.
+  ++a_iter_1;
+  ++a_iter_2;
+  ASSERT_NE(a_iter_1, root_iter_1.end());
+  ASSERT_NE(a_iter_2, root_iter_2.end());
+  ASSERT_EQ(a_iter_1, a_iter_2);
+  ASSERT_EQ(*a_iter_1, *a_iter_2);
+
+  // Move to the end of the <a> tag.
+  ++a_iter_1;
+  ++a_iter_2;
+  ASSERT_EQ(a_iter_1, root_iter_1.end());
+  ASSERT_EQ(a_iter_2, root_iter_2.end());
+  ASSERT_EQ(a_iter_1, a_iter_2);
+  ASSERT_EQ(*a_iter_1, *a_iter_2);
+}
+
+TEST(XmlParserTests, Backtracking) {
+  auto xml = CreateTestParser("res/xml/test.xml");
+  ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+  // Start at the <a> tag.
+  auto root_iter_1 = (*xml)->tree_iterator();
+
+  // Start at the <b> tag.
+  auto a_iter_1 = root_iter_1.begin();
+
+  // Start a second iterator at the <a> tag.
+  auto root_iter_2 = root_iter_1;
+  ASSERT_EQ(root_iter_1, root_iter_2);
+  ASSERT_EQ(*root_iter_1, *root_iter_2);
+
+  // Move the first iterator to the end of the <a> tag.
+  auto root_iter_end_1 = root_iter_1.end();
+  ++root_iter_1;
+  ASSERT_NE(root_iter_1, root_iter_2);
+  ASSERT_NE(*root_iter_1, *root_iter_2);
+
+  // Move to the <d> tag.
+  ++a_iter_1;
+  ASSERT_NE(a_iter_1, root_iter_end_1);
+
+  // Move to the end of the <a> tag.
+  ++a_iter_1;
+  ASSERT_EQ(a_iter_1, root_iter_end_1);
+}
+
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp
deleted file mode 100644
index df63211..0000000
--- a/cmds/idmap2/tests/XmlTests.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdio>  // fclose
-
-#include "TestHelpers.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "idmap2/Xml.h"
-#include "idmap2/ZipFile.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace android::idmap2 {
-
-TEST(XmlTests, Create) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  auto data = zip->Uncompress("AndroidManifest.xml");
-  ASSERT_THAT(data, NotNull());
-
-  auto xml = Xml::Create(data->buf, data->size);
-  ASSERT_THAT(xml, NotNull());
-
-  fclose(stderr);  // silence expected warnings from libandroidfw
-  const char* not_xml = "foo";
-  auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
-  ASSERT_THAT(fail, IsNull());
-}
-
-TEST(XmlTests, FindTag) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  auto data = zip->Uncompress("res/xml/test.xml");
-  ASSERT_THAT(data, NotNull());
-
-  auto xml = Xml::Create(data->buf, data->size);
-  ASSERT_THAT(xml, NotNull());
-
-  auto attrs = xml->FindTag("c");
-  ASSERT_THAT(attrs, NotNull());
-  ASSERT_EQ(attrs->size(), 4U);
-  ASSERT_EQ(attrs->at("type_string"), "fortytwo");
-  ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42);
-  ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42);
-  ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0U);
-
-  auto fail = xml->FindTag("does-not-exist");
-  ASSERT_THAT(fail, IsNull());
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
index 619bb6c..cf3691c 100644
--- a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
@@ -16,8 +16,11 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="test.overlay">
+
     <application android:hasCode="false"/>
+
     <overlay
         android:targetPackage="test.target"
-        android:targetName="TestResources"/>
+        android:targetName="TestResources"
+        android:resourcesMap="@xml/overlays"/>
 </manifest>
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
index 68b9f50..114b099 100755
--- a/cmds/idmap2/tests/data/overlay/build
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
+FRAMEWORK_RES_APK=${ANDROID_PRODUCT_OUT}/system/framework/framework-res.apk
 
 aapt2 compile --dir res -o compiled.flata
 
@@ -51,4 +51,12 @@
     -o overlay-static-2.apk \
     compiled.flata
 
+aapt2 link \
+    --no-resource-removal \
+    --shared-lib \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifest.xml \
+    -o overlay-shared.apk \
+    compiled.flata
+
 rm compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
index 18ee43d..7c25985 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
index 6425190..c75f3e1 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
new file mode 100644
index 0000000..93dcc82
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
index 642ab90..5b8a6e4 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
index 2ec5602..698a1fd 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
index 5842da4..1db303f 100644
--- a/cmds/idmap2/tests/data/overlay/overlay.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml
new file mode 100644
index 0000000..edd33f7
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<overlay>
+    <item target="string/str1" value="@string/str1"/>
+    <item target="string/str3" value="@string/str3" />
+    <item target="string/str4" value="@string/str4" />
+    <item target="integer/int1" value="@integer/int1" />
+    <item target="integer/not_in_target" value="@integer/not_in_target" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml
new file mode 100644
index 0000000..aa7fefa
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<overlay>
+    <item target="string/str1" value="@android:string/ok"/>
+    <item target="string/str3" value="@string/str3" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml
new file mode 100644
index 0000000..e12b823
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<overlay>
+    <item target="string/str1" value="Hello World"/>
+    <item target="integer/int1" value="73" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml
new file mode 100644
index 0000000..5728e67
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<overlay>
+    <item target="string/str1" value="@string/str4"/>
+    <item target="string/str3" value="@string/str1" />
+    <item target="string/str4" value="@string/str3" />
+    <item target="integer/int_not_in_target" value="@integer/int1" />
+</overlay>
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
index 9ebfae4..7119d82 100644
--- a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
+++ b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
@@ -25,6 +25,7 @@
     <string name="policy_signature">policy_signature</string>
     <string name="policy_odm">policy_odm</string>
     <string name="policy_oem">policy_oem</string>
+    <string name="policy_actor">policy_actor</string>
 
     <!-- Requests to overlay a resource that is not declared as overlayable. -->
     <string name="not_overlayable">not_overlayable</string>
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
index 1456e74..bd99098 100644
--- a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
+++ b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 8389f56..ad4cd48 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -41,6 +41,10 @@
         <item type="string" name="policy_oem" />
     </policy>
 
+    <policy type="actor">
+        <item type="string" name="policy_actor" />
+    </policy>
+
     <!-- Resources publicly overlayable -->
     <policy type="public">
         <item type="string" name="policy_public" />
@@ -63,4 +67,4 @@
         <item type="string" name="other" />
     </policy>
 </overlayable>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml
index a892c98..5230e25 100644
--- a/cmds/idmap2/tests/data/target/res/values/values.xml
+++ b/cmds/idmap2/tests/data/target/res/values/values.xml
@@ -36,6 +36,7 @@
     <string name="policy_signature">policy_signature</string>
     <string name="policy_system">policy_system</string>
     <string name="policy_system_vendor">policy_system_vendor</string>
+    <string name="policy_actor">policy_actor</string>
 
     <string name="other">other</string>
 </resources>
diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml
index 0fe21c6..56a3f7f 100644
--- a/cmds/idmap2/tests/data/target/res/xml/test.xml
+++ b/cmds/idmap2/tests/data/target/res/xml/test.xml
@@ -14,12 +14,15 @@
      limitations under the License.
 -->
 <a>
-    <b>
-        <c
-            type_string="fortytwo"
-            type_int_dec="42"
-            type_int_hex="0x2a"
-            type_int_boolean="true"
-            />
+    <b type_string="fortytwo"
+       type_int_dec="42"
+       type_int_hex="0x2a"
+       type_int_boolean="true">
+
+        <c />
     </b>
-</a>
+
+    <d>
+        <e />
+    </d>
+</a>
\ No newline at end of file
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index 033305a..58504a7 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 9bcd6dc..c80e5eb 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/cmds/idmap2/valgrind.sh b/cmds/idmap2/valgrind.sh
new file mode 100755
index 0000000..b4ebab0
--- /dev/null
+++ b/cmds/idmap2/valgrind.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+#
+
+function _log()
+{
+    echo -e "$*" >&2
+}
+
+function _eval()
+{
+    local label="$1"
+    local cmd="$2"
+    local red="\e[31m"
+    local green="\e[32m"
+    local reset="\e[0m"
+    local output
+
+    _log "${green}[ RUN      ]${reset} ${label}"
+    output="$(eval "$cmd" 2>&1)"
+    if [[ $? -eq 0 ]]; then
+        _log "${green}[       OK ]${reset} ${label}"
+        return 0
+    else
+        echo "${output}"
+        _log "${red}[  FAILED  ]${reset} ${label}"
+        errors=$((errors + 1))
+        return 1
+    fi
+}
+
+errors=0
+script="$(readlink -f "$BASH_SOURCE")"
+prefix="$(dirname "$script")"
+target_path="${prefix}/tests/data/target/target.apk"
+overlay_path="${prefix}/tests/data/overlay/overlay.apk"
+idmap_path="/tmp/a.idmap"
+valgrind="valgrind --error-exitcode=1 -q --track-origins=yes --leak-check=full"
+
+_eval "idmap2 create" "$valgrind idmap2 create --policy public --target-apk-path $target_path --overlay-apk-path $overlay_path --idmap-path $idmap_path"
+_eval "idmap2 dump" "$valgrind idmap2 dump --idmap-path $idmap_path"
+_eval "idmap2 lookup" "$valgrind idmap2 lookup --idmap-path $idmap_path --config '' --resid test.target:string/str1"
+_eval "idmap2 scan" "$valgrind idmap2 scan --input-directory ${prefix}/tests/data/overlay --recursive --target-package-name test.target --target-apk-path $target_path --output-directory /tmp --override-policy public"
+_eval "idmap2 verify" "$valgrind idmap2 verify --idmap-path $idmap_path"
+_eval "idmap2_tests" "$valgrind $ANDROID_HOST_OUT/nativetest64/idmap2_tests/idmap2_tests"
+exit $errors
diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp
index 9e9dac1..94855aa 100644
--- a/cmds/incident/Android.bp
+++ b/cmds/incident/Android.bp
@@ -26,7 +26,7 @@
         "libcutils",
         "liblog",
         "libutils",
-        "libincident",
+        "libincidentpriv",
     ],
 
     static_libs: [
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index d6c6c39..6e0bd06 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -231,6 +231,7 @@
     fprintf(out, "  -l           list available sections\n");
     fprintf(out, "  -p           privacy spec, LOCAL, EXPLICIT or AUTOMATIC. Default AUTOMATIC.\n");
     fprintf(out, "  -r REASON    human readable description of why the report is taken.\n");
+    fprintf(out, "  -z           gzip the incident report, i.e. pipe the output through gzip.\n");
     fprintf(out, "\n");
     fprintf(out, "and one of these destinations:\n");
     fprintf(out, "  -b           (default) print the report to stdout (in proto format)\n");
@@ -255,7 +256,7 @@
 
     // Parse the args
     int opt;
-    while ((opt = getopt(argc, argv, "bhdlp:r:s:u")) != -1) {
+    while ((opt = getopt(argc, argv, "bhdlp:r:s:uz")) != -1) {
         switch (opt) {
             case 'h':
                 usage(stdout);
@@ -302,6 +303,9 @@
                 destination = DEST_BROADCAST;
                 receiverArg = optarg;
                 break;
+            case 'z':
+                args.setGzip(true);
+                break;
             default:
                 usage(stderr);
                 return 1;
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index d7b6d69..f07743e 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -1,3 +1,28 @@
+// 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.
+
+java_binary {
+    name: "incident-helper-cmd",
+    wrapper: "incident_helper_cmd",
+    srcs: [
+        "java/**/*.java",
+    ],
+    proto: {
+        plugin: "javastream",
+    },
+}
+
 cc_defaults {
     name: "incident_helper_defaults",
 
@@ -19,7 +44,7 @@
         "src/ih_util.cpp",
     ],
 
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     shared_libs: [
         "libbase",
diff --git a/cmds/incident_helper/incident_helper_cmd b/cmds/incident_helper/incident_helper_cmd
new file mode 100644
index 0000000..d45f7df
--- /dev/null
+++ b/cmds/incident_helper/incident_helper_cmd
@@ -0,0 +1,6 @@
+#!/system/bin/sh
+# Script to start "incident_helper_cmd" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/incident-helper-cmd.jar
+exec app_process $base/bin com.android.commands.incident.IncidentHelper "$@"
diff --git a/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
new file mode 100644
index 0000000..d97b17e
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.commands.incident;
+
+/**
+ * Thrown when there is an error executing a section.
+ */
+public class ExecutionException extends Exception {
+    /**
+     * Constructs a ExecutionException.
+     *
+     * @param msg the message
+     */
+    public ExecutionException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a ExecutionException from another exception.
+     *
+     * @param e the exception
+     */
+    public ExecutionException(Exception e) {
+        super(e);
+    }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java
new file mode 100644
index 0000000..e5874e0
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.commands.incident;
+
+import android.util.Log;
+
+import com.android.commands.incident.sections.PersistLogSection;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Helper command runner for incidentd to run customized command to gather data for a non-standard
+ * section.
+ */
+public class IncidentHelper {
+    private static final String TAG = "IncidentHelper";
+    private static boolean sLog = false;
+    private final List<String> mArgs;
+    private ListIterator<String> mArgsIterator;
+
+    private IncidentHelper(String[] args) {
+        mArgs = Collections.unmodifiableList(Arrays.asList(args));
+        mArgsIterator = mArgs.listIterator();
+    }
+
+    private static void showUsage(PrintStream out) {
+        out.println("This command is not designed to be run manually.");
+        out.println("Usage:");
+        out.println("  run [sectionName]");
+    }
+
+    private void run(String[] args) throws ExecutionException {
+        Section section = null;
+        List<String> sectionArgs = new ArrayList<>();
+        while (mArgsIterator.hasNext()) {
+            String arg = mArgsIterator.next();
+            if ("-l".equals(arg)) {
+                sLog = true;
+                Log.i(TAG, "Args: [" + String.join(",", args) + "]");
+            } else if ("run".equals(arg)) {
+                section = getSection(nextArgRequired());
+                mArgsIterator.forEachRemaining(sectionArgs::add);
+                break;
+            } else {
+                log(Log.WARN, TAG, "Error: Unknown argument: " + arg);
+                return;
+            }
+        }
+        section.run(System.in, System.out, sectionArgs);
+    }
+
+    private static Section getSection(String name) throws IllegalArgumentException {
+        if ("persisted_logs".equals(name)) {
+            return new PersistLogSection();
+        }
+        throw new IllegalArgumentException("Section not found: " + name);
+    }
+
+    private String nextArgRequired() {
+        if (!mArgsIterator.hasNext()) {
+            throw new IllegalArgumentException(
+                    "Arg required after \"" + mArgs.get(mArgsIterator.previousIndex()) + "\"");
+        }
+        return mArgsIterator.next();
+    }
+
+    /**
+     * Print the given message to stderr, also log it if asked to (set by -l cmd arg).
+     */
+    public static void log(int priority, String tag, String msg) {
+        System.err.println(tag + ": " + msg);
+        if (sLog) {
+            Log.println(priority, tag, msg);
+        }
+    }
+
+    /**
+     * Command-line entry point.
+     *
+     * @param args The command-line arguments
+     */
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            showUsage(System.err);
+            System.exit(0);
+        }
+        IncidentHelper incidentHelper = new IncidentHelper(args);
+        try {
+            incidentHelper.run(args);
+        } catch (IllegalArgumentException e) {
+            showUsage(System.err);
+            System.err.println();
+            e.printStackTrace(System.err);
+            if (sLog) {
+                Log.e(TAG, "Error: ", e);
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+            if (sLog) {
+                Log.e(TAG, "Error: ", e);
+            }
+            System.exit(1);
+        }
+    }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/Section.java b/cmds/incident_helper/java/com/android/commands/incident/Section.java
new file mode 100644
index 0000000..1c8c657
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/Section.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.commands.incident;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/** Section interface used by {@link IncidentHelper}. */
+public interface Section {
+    /**
+     * Writes protobuf wire format to out, optionally reads data from in, with supplied args.
+     */
+    void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException;
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java
new file mode 100644
index 0000000..f9d2e79
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.commands.incident.sections;
+
+import android.util.Log;
+import android.util.PersistedLogProto;
+import android.util.TextLogEntry;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.commands.incident.ExecutionException;
+import com.android.commands.incident.IncidentHelper;
+import com.android.commands.incident.Section;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** PersistLogSection reads persisted logs and parses them into a PersistedLogProto. */
+public class PersistLogSection implements Section {
+    private static final String TAG = "IH_PersistLog";
+    private static final String LOG_DIR = "/data/misc/logd/";
+    // Persist log files are named logcat, logcat.001, logcat.002, logcat.003, ...
+    private static final Pattern LOG_FILE_RE = Pattern.compile("logcat(\\.\\d+)?");
+    private static final Pattern BUFFER_BEGIN_RE =
+            Pattern.compile("--------- (?:beginning of|switch to) (.*)");
+    private static final Map<String, Long> SECTION_NAME_TO_ID = new HashMap<>();
+    private static final Map<Character, Integer> LOG_PRIORITY_MAP = new HashMap<>();
+    private static final String DEFAULT_BUFFER = "main";
+
+    static {
+        SECTION_NAME_TO_ID.put("main", PersistedLogProto.MAIN_LOGS);
+        SECTION_NAME_TO_ID.put("radio", PersistedLogProto.RADIO_LOGS);
+        SECTION_NAME_TO_ID.put("events", PersistedLogProto.EVENTS_LOGS);
+        SECTION_NAME_TO_ID.put("system", PersistedLogProto.SYSTEM_LOGS);
+        SECTION_NAME_TO_ID.put("crash", PersistedLogProto.CRASH_LOGS);
+        SECTION_NAME_TO_ID.put("kernel", PersistedLogProto.KERNEL_LOGS);
+    }
+
+    static {
+        LOG_PRIORITY_MAP.put('V', TextLogEntry.LOG_VERBOSE);
+        LOG_PRIORITY_MAP.put('D', TextLogEntry.LOG_DEBUG);
+        LOG_PRIORITY_MAP.put('I', TextLogEntry.LOG_INFO);
+        LOG_PRIORITY_MAP.put('W', TextLogEntry.LOG_WARN);
+        LOG_PRIORITY_MAP.put('E', TextLogEntry.LOG_ERROR);
+        LOG_PRIORITY_MAP.put('F', TextLogEntry.LOG_FATAL);
+        LOG_PRIORITY_MAP.put('S', TextLogEntry.LOG_SILENT);
+    }
+
+    /**
+     * Caches dates at 00:00:00 to epoch second elapsed conversion. There are only a few different
+     * dates in persisted logs in one device, and constructing DateTime object is relatively
+     * expensive.
+     */
+    private Map<Integer, Long> mEpochTimeCache = new HashMap<>();
+    private ProtoOutputStream mProto;
+    private long mCurrFieldId;
+    private long mMaxBytes = Long.MAX_VALUE;
+
+    @Override
+    public void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException {
+        parseArgs(args);
+        Path logDirPath = Paths.get(LOG_DIR);
+        if (!Files.exists(logDirPath)) {
+            IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " does not exist.");
+            return;
+        }
+        if (!Files.isReadable(logDirPath)) {
+            IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " is not readable.");
+            return;
+        }
+        mProto = new ProtoOutputStream(out);
+        setCurrentSection(DEFAULT_BUFFER);
+        final Matcher logFileRe = LOG_FILE_RE.matcher("");
+        // Need to process older log files first and write logs to proto in chronological order
+        // But we want to process only the latest ones if there is a size limit
+        try (Stream<File> stream = Files.list(logDirPath).map(Path::toFile)
+                .filter(f -> !f.isDirectory() && match(logFileRe, f.getName()) != null)
+                .sorted(Comparator.comparingLong(File::lastModified).reversed())) {
+            Iterator<File> iter = stream.iterator();
+            List<File> filesToProcess = new ArrayList<>();
+            long sumBytes = 0;
+            while (iter.hasNext()) {
+                File file = iter.next();
+                sumBytes += file.length();
+                if (sumBytes > mMaxBytes) {
+                    break;
+                }
+                filesToProcess.add(file);
+            }
+            IncidentHelper.log(Log.INFO, TAG, "Limit # log files to " + filesToProcess.size());
+            filesToProcess.stream()
+                    .sorted(Comparator.comparingLong(File::lastModified))
+                    .forEachOrdered(this::processFile);
+        } catch (IOException e) {
+            throw new ExecutionException(e);
+        } finally {
+            mProto.flush();
+        }
+        IncidentHelper.log(Log.DEBUG, TAG, "Bytes written: " + mProto.getBytes().length);
+    }
+
+    private void parseArgs(List<String> args) {
+        Iterator<String> iter = args.iterator();
+        while (iter.hasNext()) {
+            String arg = iter.next();
+            if ("--limit".equals(arg) && iter.hasNext()) {
+                String sizeStr = iter.next().toLowerCase();
+                if (sizeStr.endsWith("mb")) {
+                    mMaxBytes = Long.parseLong(sizeStr.replace("mb", "")) * 1024 * 1024;
+                } else if (sizeStr.endsWith("kb")) {
+                    mMaxBytes = Long.parseLong(sizeStr.replace("kb", "")) * 1024;
+                } else {
+                    mMaxBytes = Long.parseLong(sizeStr);
+                }
+            } else {
+                throw new IllegalArgumentException("Unknown argument: " + arg);
+            }
+        }
+    }
+
+    private void processFile(File file) {
+        final Matcher bufferBeginRe = BUFFER_BEGIN_RE.matcher("");
+        try (BufferedReader reader = Files.newBufferedReader(file.toPath(),
+                StandardCharsets.UTF_8)) {
+            String line;
+            Matcher m;
+            while ((line = reader.readLine()) != null) {
+                if ((m = match(bufferBeginRe, line)) != null) {
+                    setCurrentSection(m.group(1));
+                    continue;
+                }
+                parseLine(line);
+            }
+        } catch (IOException e) {
+            // Non-fatal error. We can skip and still process other files.
+            IncidentHelper.log(Log.WARN, TAG, "Error reading \"" + file + "\": " + e.getMessage());
+        }
+        IncidentHelper.log(Log.DEBUG, TAG, "Finished reading " + file);
+    }
+
+    private void setCurrentSection(String sectionName) {
+        Long sectionId = SECTION_NAME_TO_ID.get(sectionName);
+        if (sectionId == null) {
+            IncidentHelper.log(Log.WARN, TAG, "Section does not exist: " + sectionName);
+            sectionId = SECTION_NAME_TO_ID.get(DEFAULT_BUFFER);
+        }
+        mCurrFieldId = sectionId;
+    }
+
+    /**
+     * Parse a log line in the following format:
+     * 01-01 15:01:47.723501  2738  2895 I Exp_TAG: example log line
+     *
+     * It does not use RegExp for performance reasons. Using this RegExp "(\\d{2})-(\\d{2})\\s
+     * (\\d{2}):(\\d{2}):(\\d{2}).(\\d{6})\\s+(\\d+)\\s+(\\d+)\\s+(.)\\s+(.*?):\\s(.*)" is twice as
+     * slow as the current approach.
+     */
+    private void parseLine(String line) {
+        long token = mProto.start(mCurrFieldId);
+        try {
+            mProto.write(TextLogEntry.SEC, getEpochSec(line));
+            // Nanosec is 15th to 20th digits of "10-01 02:57:27.710652" times 1000
+            mProto.write(TextLogEntry.NANOSEC, parseInt(line, 15, 21) * 1000L);
+
+            int start = nextNonBlank(line, 21);
+            int end = line.indexOf(' ', start + 1);
+            mProto.write(TextLogEntry.PID, parseInt(line, start, end));
+
+            start = nextNonBlank(line, end);
+            end = line.indexOf(' ', start + 1);
+            mProto.write(TextLogEntry.TID, parseInt(line, start, end));
+
+            start = nextNonBlank(line, end);
+            char priority = line.charAt(start);
+            mProto.write(TextLogEntry.PRIORITY,
+                    LOG_PRIORITY_MAP.getOrDefault(priority, TextLogEntry.LOG_DEFAULT));
+
+            start = nextNonBlank(line, start + 1);
+            end = line.indexOf(": ", start);
+            mProto.write(TextLogEntry.TAG, line.substring(start, end).trim());
+            mProto.write(TextLogEntry.LOG, line.substring(Math.min(end + 2, line.length())));
+        } catch (RuntimeException e) {
+            // Error reporting is likely piped to /dev/null. Inserting it into the proto to make
+            // it more useful.
+            mProto.write(TextLogEntry.SEC, System.currentTimeMillis() / 1000);
+            mProto.write(TextLogEntry.PRIORITY, TextLogEntry.LOG_ERROR);
+            mProto.write(TextLogEntry.TAG, TAG);
+            mProto.write(TextLogEntry.LOG,
+                    "Error parsing \"" + line + "\"" + ": " + e.getMessage());
+        }
+        mProto.end(token);
+    }
+
+    // ============== Below are util methods to parse log lines ==============
+
+    private static int nextNonBlank(String line, int start) {
+        for (int i = start; i < line.length(); i++) {
+            if (line.charAt(i) != ' ') {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Gets the epoch second from the line string. Line starts with a fixed-length timestamp like
+     * "10-01 02:57:27.710652"
+     */
+    private long getEpochSec(String line) {
+        int month = getDigit(line, 0) * 10 + getDigit(line, 1);
+        int day = getDigit(line, 3) * 10 + getDigit(line, 4);
+
+        int mmdd = month * 100 + day;
+        long epochSecBase = mEpochTimeCache.computeIfAbsent(mmdd, (key) -> {
+            final GregorianCalendar calendar = new GregorianCalendar();
+            calendar.set(Calendar.MONTH, (month + 12 - 1) % 12);
+            calendar.set(Calendar.DAY_OF_MONTH, day);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+            // Date in log entries can never be in the future. If it happens, it means we are off
+            // by one year.
+            if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
+                calendar.roll(Calendar.YEAR, /*amount=*/-1);
+            }
+            return calendar.getTimeInMillis() / 1000;
+        });
+
+        int hh = getDigit(line, 6) * 10 + getDigit(line, 7);
+        int mm = getDigit(line, 9) * 10 + getDigit(line, 10);
+        int ss = getDigit(line, 12) * 10 + getDigit(line, 13);
+        return epochSecBase + hh * 3600 + mm * 60 + ss;
+    }
+
+    private static int parseInt(String line, /*inclusive*/ int start, /*exclusive*/ int end) {
+        int num = 0;
+        for (int i = start; i < end; i++) {
+            num = num * 10 + getDigit(line, i);
+        }
+        return num;
+    }
+
+    private static int getDigit(String str, int pos) {
+        int digit = str.charAt(pos) - '0';
+        if (digit < 0 || digit > 9) {
+            throw new NumberFormatException("'" + str.charAt(pos) + "' is not a digit.");
+        }
+        return digit;
+    }
+
+    private static Matcher match(Matcher matcher, String text) {
+        matcher.reset(text);
+        return matcher.matches() ? matcher : null;
+    }
+}
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 77a56e5..557fe9f 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -237,33 +237,38 @@
 Reader::Reader(const int fd)
 {
     mFile = fdopen(fd, "r");
+    mBuffer = new char[1024];
     mStatus = mFile == nullptr ? "Invalid fd " + std::to_string(fd) : "";
 }
 
 Reader::~Reader()
 {
     if (mFile != nullptr) fclose(mFile);
+    delete[] mBuffer;
 }
 
 bool Reader::readLine(std::string* line) {
     if (mFile == nullptr) return false;
 
-    char* buf = nullptr;
     size_t len = 0;
-    ssize_t read = getline(&buf, &len, mFile);
+    ssize_t read = getline(&mBuffer, &len, mFile);
     if (read != -1) {
-        std::string s(buf);
+        std::string s(mBuffer);
         line->assign(trim(s, DEFAULT_NEWLINE));
-    } else if (errno == EINVAL) {
-        mStatus = "Bad Argument";
+        return true;
     }
-    free(buf);
-    return read != -1;
+    if (!feof(mFile)) {
+        mStatus = "Error reading file. Ferror: " + std::to_string(ferror(mFile));
+    }
+    return false;
 }
 
 bool Reader::ok(std::string* error) {
+    if (mStatus.empty()) {
+        return true;
+    }
     error->assign(mStatus);
-    return mStatus.empty();
+    return false;
 }
 
 // ==============================================================================
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 09dc8e6..5812c60 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -117,6 +117,7 @@
 
 private:
     FILE* mFile;
+    char* mBuffer;
     std::string mStatus;
 };
 
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index 25e0328..c47526a 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -43,7 +43,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     proto: {
         type: "lite",
@@ -54,7 +54,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotoutil",
         "libservices",
@@ -98,7 +98,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     srcs: [
         "tests/**/*.cpp",
@@ -128,7 +128,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotobuf-cpp-full",
         "libprotoutil",
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index d295b84..78c322e 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -17,6 +17,7 @@
 #include "Log.h"
 
 #include "FdBuffer.h"
+#include "incidentd_util.h"
 
 #include <log/log.h>
 #include <utils/SystemClock.h>
@@ -31,17 +32,24 @@
 namespace incidentd {
 
 const ssize_t BUFFER_SIZE = 16 * 1024;  // 16 KB
-const ssize_t MAX_BUFFER_COUNT = 6144;   // 96 MB max
+const ssize_t MAX_BUFFER_SIZE = 96 * 1024 * 1024;  // 96 MB
 
-FdBuffer::FdBuffer()
-        :mBuffer(new EncodedBuffer(BUFFER_SIZE)),
+FdBuffer::FdBuffer(): FdBuffer(get_buffer_from_pool(), /* isBufferPooled= */ true)  {
+}
+
+FdBuffer::FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled)
+        :mBuffer(buffer),
          mStartTime(-1),
          mFinishTime(-1),
          mTimedOut(false),
-         mTruncated(false) {
+         mTruncated(false),
+         mIsBufferPooled(isBufferPooled) {
 }
 
 FdBuffer::~FdBuffer() {
+    if (mIsBufferPooled) {
+        return_buffer_to_pool(mBuffer);
+    }
 }
 
 status_t FdBuffer::read(int fd, int64_t timeout) {
@@ -51,7 +59,7 @@
     fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
 
     while (true) {
-        if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+        if (mBuffer->size() >= MAX_BUFFER_SIZE) {
             mTruncated = true;
             VLOG("Truncating data");
             break;
@@ -106,7 +114,7 @@
     mStartTime = uptimeMillis();
 
     while (true) {
-        if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+        if (mBuffer->size() >= MAX_BUFFER_SIZE) {
             // Don't let it get too big.
             mTruncated = true;
             VLOG("Truncating data");
@@ -156,7 +164,7 @@
 
     // This is the buffer used to store processed data
     while (true) {
-        if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+        if (mBuffer->size() >= MAX_BUFFER_SIZE) {
             VLOG("Truncating data");
             mTruncated = true;
             break;
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index a349360..9b2794d 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -35,6 +35,7 @@
 class FdBuffer {
 public:
     FdBuffer();
+    FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled = false);
     ~FdBuffer();
 
     /**
@@ -114,6 +115,7 @@
     int64_t mFinishTime;
     bool mTimedOut;
     bool mTruncated;
+    bool mIsBufferPooled;
 };
 
 }  // namespace incidentd
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..dc16125 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
 
 // ================================================================================
 ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler)
+                             const sp<Broadcaster>& broadcaster,
+                             const sp<Looper>& handlerLooper,
+                             const sp<Throttler>& throttler,
+                             const vector<BringYourOwnSection*>& registeredSections)
         :mLock(),
          mWorkDirectory(workDirectory),
          mBroadcaster(broadcaster),
          mHandlerLooper(handlerLooper),
          mBacklogDelay(DEFAULT_DELAY_NS),
          mThrottler(throttler),
+         mRegisteredSections(registeredSections),
          mBatch(new ReportBatch()) {
 }
 
@@ -149,6 +152,7 @@
 }
 
 void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
+    unique_lock<mutex> lock(mLock);
     mBatch->addPersistedReport(args);
     mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
     mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
@@ -156,6 +160,7 @@
 
 void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
         const sp<IIncidentReportStatusListener>& listener, int streamFd) {
+    unique_lock<mutex> lock(mLock);
     mBatch->addStreamingReport(args, listener, streamFd);
     mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
     mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
@@ -185,7 +190,7 @@
         return;
     }
 
-    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
 
     // Take the report, which might take a while. More requests might queue
     // up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +242,7 @@
     mWorkDirectory = new WorkDirectory();
     mBroadcaster = new Broadcaster(mWorkDirectory);
     mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
-            mThrottler);
+            mThrottler, mRegisteredSections);
     mBroadcaster->setHandler(mHandler);
 }
 
@@ -327,6 +332,11 @@
             incidentArgs.addSection(id);
         }
     }
+    for (const Section* section : mRegisteredSections) {
+        if (!section_requires_specific_mention(section->id)) {
+            incidentArgs.addSection(section->id);
+        }
+    }
 
     // The ReportRequest takes ownership of the fd, so we need to dup it.
     int fd = dup(stream.get());
@@ -339,6 +349,46 @@
     return Status::ok();
 }
 
+Status IncidentService::registerSection(const int id, const String16& name16,
+        const sp<IIncidentDumpCallback>& callback) {
+    const String8 name = String8(name16);
+    const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d registers section %d '%s'", callingUid, id, name.c_str());
+    if (callback == nullptr) {
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+    }
+    for (int i = 0; i < mRegisteredSections.size(); i++) {
+        if (mRegisteredSections.at(i)->id == id) {
+            if (mRegisteredSections.at(i)->uid != callingUid) {
+                ALOGW("Error registering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.at(i) = new BringYourOwnSection(id, name.c_str(), callingUid, callback);
+            return Status::ok();
+        }
+    }
+    mRegisteredSections.push_back(new BringYourOwnSection(id, name.c_str(), callingUid, callback));
+    return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+    uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d unregisters section %d", callingUid, id);
+
+    for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+        if ((*it)->id == id) {
+            if ((*it)->uid != callingUid) {
+                ALOGW("Error unregistering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.erase(it);
+            return Status::ok();
+        }
+    }
+    ALOGW("Section %d not found", id);
+    return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
 Status IncidentService::systemRunning() {
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
 using namespace android::binder;
 using namespace android::os;
 
+class BringYourOwnSection;
+
 // ================================================================================
 class ReportHandler : public MessageHandler {
 public:
     ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler);
+                  const sp<Broadcaster>& broadcaster,
+                  const sp<Looper>& handlerLooper,
+                  const sp<Throttler>& throttler,
+                  const vector<BringYourOwnSection*>& registeredSections);
     virtual ~ReportHandler();
 
     virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
     nsecs_t mBacklogDelay;
     sp<Throttler> mThrottler;
 
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
     sp<ReportBatch> mBatch;
 
     /**
@@ -126,6 +132,11 @@
     virtual Status reportIncidentToDumpstate(unique_fd stream,
             const sp<IIncidentReportStatusListener>& listener);
 
+    virtual Status registerSection(int id, const String16& name,
+            const sp<IIncidentDumpCallback>& callback);
+
+    virtual Status unregisterSection(int id);
+
     virtual Status systemRunning();
 
     virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
     sp<Broadcaster> mBroadcaster;
     sp<ReportHandler> mHandler;
     sp<Throttler> mThrottler;
+    vector<BringYourOwnSection*> mRegisteredSections;
 
     /**
      * Commands print out help.
diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp
index d00ecdd..0d427d1 100644
--- a/cmds/incidentd/src/PrivacyFilter.cpp
+++ b/cmds/incidentd/src/PrivacyFilter.cpp
@@ -19,9 +19,6 @@
 #include "incidentd_util.h"
 #include "PrivacyFilter.h"
 #include "proto_util.h"
-
-#include "incidentd_util.h"
-#include "proto_util.h"
 #include "Section.h"
 
 #include <android-base/file.h>
@@ -129,6 +126,8 @@
     FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
             uint8_t bufferLevel);
 
+    ~FieldStripper();
+
     /**
      * Take the data that we have, and filter it down so that no fields
      * are more sensitive than the given privacy policy.
@@ -167,6 +166,7 @@
      */
     uint8_t mCurrentLevel;
 
+    sp<EncodedBuffer> mEncodedBuffer;
 };
 
 FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
@@ -174,19 +174,25 @@
         :mRestrictions(restrictions),
          mData(data),
          mSize(data->size()),
-         mCurrentLevel(bufferLevel) {
+         mCurrentLevel(bufferLevel),
+         mEncodedBuffer(get_buffer_from_pool()) {
     if (mSize < 0) {
         ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size."
                 " Data will be missing.");
     }
 }
 
+FieldStripper::~FieldStripper() {
+    return_buffer_to_pool(mEncodedBuffer);
+}
+
 status_t FieldStripper::strip(const uint8_t privacyPolicy) {
     // If the current strip level is less (fewer fields retained) than what's already in the
     // buffer, then we can skip it.
     if (mCurrentLevel < privacyPolicy) {
         PrivacySpec spec(privacyPolicy);
-        ProtoOutputStream proto;
+        mEncodedBuffer->clear();
+        ProtoOutputStream proto(mEncodedBuffer);
 
         // Optimization when no strip happens.
         if (mRestrictions == NULL || spec.RequireAll()) {
@@ -267,7 +273,7 @@
     // Order the writes by privacy filter, with increasing levels of filtration,k
     // so we can do the filter once, and then write many times.
     sort(mOutputs.begin(), mOutputs.end(),
-        [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { 
+        [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
             return a->getPrivacyPolicy() < b->getPrivacyPolicy();
         });
 
@@ -370,7 +376,7 @@
             write_field_or_skip(NULL, reader, fieldTag, true);
         }
     }
-
+    clear_buffer_pool();
     err = reader->getError();
     if (err != NO_ERROR) {
         ALOGW("filter_and_write_report reader had an error: %s", strerror(-err));
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..86a78f09 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,10 +35,12 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <sys/prctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <string>
 #include <time.h>
+#include <wait.h>
 
 namespace android {
 namespace os {
@@ -51,6 +53,8 @@
  *      frameworks/base/core/proto/android/os/incident.proto
  */
 const int FIELD_ID_METADATA = 2;
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
 
 IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) {
     switch (privacyPolicy) {
@@ -142,7 +146,8 @@
          mListener(listener),
          mFd(fd),
          mIsStreaming(fd >= 0),
-         mStatus(NO_ERROR) {
+         mStatus(OK),
+         mZipPid(-1) {
 }
 
 ReportRequest::~ReportRequest() {
@@ -153,7 +158,14 @@
 }
 
 bool ReportRequest::ok() {
-    return mFd >= 0 && mStatus == NO_ERROR;
+    if (mStatus != OK) {
+        return false;
+    }
+    if (!args.gzip()) {
+        return mFd >= 0;
+    }
+    // Send a blank signal to check if mZipPid is alive
+    return mZipPid > 0 && kill(mZipPid, 0) == 0;
 }
 
 bool ReportRequest::containsSection(int sectionId) const {
@@ -161,10 +173,45 @@
 }
 
 void ReportRequest::closeFd() {
-    if (mIsStreaming && mFd >= 0) {
+    if (!mIsStreaming) {
+        return;
+    }
+    if (mFd >= 0) {
         close(mFd);
         mFd = -1;
     }
+    if (mZipPid > 0) {
+        mZipPipe.close();
+        // Gzip may take some time.
+        status_t err = wait_child(mZipPid, /* timeout_ms= */ 10 * 1000);
+        if (err != 0) {
+            ALOGW("[ReportRequest] abnormal child process: %s", strerror(-err));
+        }
+    }
+}
+
+int ReportRequest::getFd() {
+    return mZipPid > 0 ? mZipPipe.writeFd().get() : mFd;
+}
+
+status_t ReportRequest::initGzipIfNecessary() {
+    if (!mIsStreaming || !args.gzip()) {
+        return OK;
+    }
+    if (!mZipPipe.init()) {
+        ALOGE("[ReportRequest] Failed to setup pipe for gzip");
+        mStatus = -errno;
+        return mStatus;
+    }
+    int status = 0;
+    pid_t pid = fork_execute_cmd((char* const*)GZIP, mZipPipe.readFd().release(), mFd, &status);
+    if (pid < 0 || status != 0) {
+        mStatus = status;
+        return mStatus;
+    }
+    mZipPid = pid;
+    mFd = -1;
+    return OK;
 }
 
 // ================================================================================
@@ -364,7 +411,6 @@
     mSectionBufferSuccess = false;
     mHadError = false;
     mSectionErrors.clear();
-    
 }
 
 void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +516,13 @@
 
 
 // ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+                   const sp<ReportBatch>& batch,
+                   const vector<BringYourOwnSection*>& registeredSections)
         :mWorkDirectory(workDirectory),
          mWriter(batch),
-         mBatch(batch) {
+         mBatch(batch),
+         mRegisteredSections(registeredSections) {
 }
 
 Reporter::~Reporter() {
@@ -560,6 +609,13 @@
         reportId = (spec.tv_sec) * 1000 + spec.tv_nsec;
     }
 
+    mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) {
+        status_t err = request->initGzipIfNecessary();
+        if (err != 0) {
+            ALOGW("Error forking gzip: %s", strerror(err));
+        }
+    });
+
     // Write the incident report headers - each request gets its own headers.  It's different
     // from the other top-level fields in IncidentReport that are the sections where the rest
     // is all shared data (although with their own individual privacy filtering).
@@ -580,50 +636,15 @@
     // For each of the report fields, see if we need it, and if so, execute the command
     // and report to those that care that we're doing it.
     for (const Section** section = SECTION_LIST; *section; section++) {
-        const int sectionId = (*section)->id;
-
-        // If nobody wants this section, skip it.
-        if (!mBatch->containsSection(sectionId)) {
-            continue;
-        }
-
-        ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
-        IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
-        // Notify listener of starting
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-            listener->onReportSectionStatus(
-                    sectionId, IIncidentReportStatusListener::STATUS_STARTING);
-        });
-
-        // Go get the data and write it into the file descriptors.
-        mWriter.startSection(sectionId);
-        err = (*section)->Execute(&mWriter);
-        mWriter.endSection(sectionMetadata);
-
-        // Sections returning errors are fatal. Most errors should not be fatal.
-        if (err != NO_ERROR) {
-            mWriter.error((*section), err, "Section failed. Stopping report.");
+        if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
             goto DONE;
         }
+    }
 
-        // The returned max data size is used for throttling too many incident reports.
-        (*reportByteSize) += sectionMetadata->report_size_bytes();
-
-        // For any requests that failed during this section, remove them now.  We do this
-        // before calling back about section finished, so listeners do not erroniously get the
-        // impression that the section succeeded.  But we do it here instead of inside
-        // writeSection so that the callback is done from a known context and not from the
-        // bowels of a section, where changing the batch could cause odd errors.
-        cancel_and_remove_failed_requests();
-
-        // Notify listener of finishing
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-                listener->onReportSectionStatus(
-                        sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
-        });
-
-        ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+    for (const Section* section : mRegisteredSections) {
+        if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+            goto DONE;
+        }
     }
 
 DONE:
@@ -677,10 +698,59 @@
             listener->onReportFailed();
         });
     }
-
+    clear_buffer_pool();
     ALOGI("Done taking incident report err=%s", strerror(-err));
 }
 
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize) {
+    const int sectionId = section->id;
+
+    // If nobody wants this section, skip it.
+    if (!mBatch->containsSection(sectionId)) {
+        return NO_ERROR;
+    }
+
+    ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+    IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+    // Notify listener of starting
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+        listener->onReportSectionStatus(
+                sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+    });
+
+    // Go get the data and write it into the file descriptors.
+    mWriter.startSection(sectionId);
+    status_t err = section->Execute(&mWriter);
+    mWriter.endSection(sectionMetadata);
+
+    // Sections returning errors are fatal. Most errors should not be fatal.
+    if (err != NO_ERROR) {
+        mWriter.error(section, err, "Section failed. Stopping report.");
+        return err;
+    }
+
+    // The returned max data size is used for throttling too many incident reports.
+    (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+    // For any requests that failed during this section, remove them now.  We do this
+    // before calling back about section finished, so listeners do not erroniously get the
+    // impression that the section succeeded.  But we do it here instead of inside
+    // writeSection so that the callback is done from a known context and not from the
+    // bowels of a section, where changing the batch could cause odd errors.
+    cancel_and_remove_failed_requests();
+
+    // Notify listener of finishing
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+            listener->onReportSectionStatus(
+                    sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+    });
+
+    ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+    return NO_ERROR;
+}
+
 void Reporter::cancel_and_remove_failed_requests() {
     // Handle a failure in the persisted file
     if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..bd47a23 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -15,12 +15,14 @@
  */
 #pragma once
 
+#include "incidentd_util.h"
 #include "FdBuffer.h"
 #include "WorkDirectory.h"
 
 #include "frameworks/base/core/proto/android/os/metadata.pb.h"
 #include <android/content/ComponentName.h>
 #include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
 #include <android/os/IncidentReportArgs.h>
 #include <android/util/protobuf.h>
 
@@ -39,6 +41,7 @@
 using namespace android::content;
 using namespace android::os;
 
+class BringYourOwnSection;
 class Section;
 
 // ================================================================================
@@ -61,10 +64,12 @@
 
     sp<IIncidentReportStatusListener> getListener() { return mListener; }
 
-    int getFd() { return mFd; }
+    int getFd();
 
     int setPersistedFd(int fd);
 
+    status_t initGzipIfNecessary();
+
     void closeFd();
 
 private:
@@ -72,6 +77,8 @@
     int mFd;
     bool mIsStreaming;
     status_t mStatus;
+    pid_t mZipPid;
+    Fpipe mZipPipe;
 };
 
 // ================================================================================
@@ -122,7 +129,7 @@
     void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
 
     /**
-     * Call func(request) for each file descriptor that has 
+     * Call func(request) for each file descriptor.
      */
     void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
 
@@ -251,7 +258,9 @@
 // ================================================================================
 class Reporter : public virtual RefBase {
 public:
-    Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+    Reporter(const sp<WorkDirectory>& workDirectory,
+             const sp<ReportBatch>& batch,
+             const vector<BringYourOwnSection*>& registeredSections);
 
     virtual ~Reporter();
 
@@ -263,6 +272,10 @@
     ReportWriter mWriter;
     sp<ReportBatch> mBatch;
     sp<ReportFile> mPersistedFile;
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
+    status_t execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize);
 
     void cancel_and_remove_failed_requests();
 };
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 6cbfd47..e56ed39 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -20,9 +20,9 @@
 
 #include <dirent.h>
 #include <errno.h>
-
 #include <mutex>
 #include <set>
+#include <thread>
 
 #include <android-base/file.h>
 #include <android-base/properties.h>
@@ -35,12 +35,14 @@
 #include <log/log_event_list.h>
 #include <log/logprint.h>
 #include <private/android_logger.h>
+#include <sys/mman.h>
 
 #include "FdBuffer.h"
 #include "Privacy.h"
 #include "frameworks/base/core/proto/android/os/backtrace.proto.h"
 #include "frameworks/base/core/proto/android/os/data.proto.h"
 #include "frameworks/base/core/proto/android/util/log.proto.h"
+#include "frameworks/base/core/proto/android/util/textdump.proto.h"
 #include "incidentd_util.h"
 
 namespace android {
@@ -104,7 +106,6 @@
         return NO_ERROR;
     }
 
-    FdBuffer buffer;
     Fpipe p2cPipe;
     Fpipe c2pPipe;
     // initiate pipes to pass data to/from incident_helper
@@ -120,6 +121,7 @@
     }
 
     // parent process
+    FdBuffer buffer;
     status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()),
                                                            std::move(c2pPipe.readFd()),
                                                            this->timeoutMs, mIsSysfs);
@@ -134,7 +136,7 @@
     status_t ihStatus = wait_child(pid);
     if (ihStatus != NO_ERROR) {
         ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus));
-        return ihStatus;
+        return OK; // Not a fatal error.
     }
 
     return writer->writeSection(buffer);
@@ -233,7 +235,7 @@
     Fpipe pipe;
 
     // Lock protects these fields
-    mutex lock;
+    std::mutex lock;
     bool workerDone;
     status_t workerError;
 
@@ -260,78 +262,42 @@
     }
 }
 
-static void* worker_thread_func(void* cookie) {
-    // Don't crash the service if we write to a closed pipe (which can happen if
-    // dumping times out).
-    signal(SIGPIPE, sigpipe_handler);
-
-    WorkerThreadData* data = (WorkerThreadData*)cookie;
-    status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
-
-    {
-        unique_lock<mutex> lock(data->lock);
-        data->workerDone = true;
-        data->workerError = err;
-    }
-
-    data->pipe.writeFd().reset();
-    data->decStrong(data->section);
-    // data might be gone now. don't use it after this point in this thread.
-    return NULL;
-}
-
 status_t WorkerThreadSection::Execute(ReportWriter* writer) const {
-    status_t err = NO_ERROR;
-    pthread_t thread;
-    pthread_attr_t attr;
-    bool workerDone = false;
-    FdBuffer buffer;
-
-    // Data shared between this thread and the worker thread.
+    // Create shared data and pipe. Don't put data on the stack since this thread may exit early.
     sp<WorkerThreadData> data = new WorkerThreadData(this);
-
-    // Create the pipe
     if (!data->pipe.init()) {
         return -errno;
     }
-
-    // Create the thread
-    err = pthread_attr_init(&attr);
-    if (err != 0) {
-        return -err;
-    }
-    // TODO: Do we need to tweak thread priority?
-    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-    if (err != 0) {
-        pthread_attr_destroy(&attr);
-        return -err;
-    }
-
-    // The worker thread needs a reference and we can't let the count go to zero
-    // if that thread is slow to start.
     data->incStrong(this);
-
-    err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get());
-    pthread_attr_destroy(&attr);
-    if (err != 0) {
+    std::thread([data, this]() {
+        // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+        signal(SIGPIPE, sigpipe_handler);
+        status_t err = data->section->BlockingCall(data->pipe.writeFd());
+        {
+            std::scoped_lock<std::mutex> lock(data->lock);
+            data->workerDone = true;
+            data->workerError = err;
+            // unique_fd is not thread safe. If we don't lock it, reset() may pause half way while
+            // the other thread executes to the end, calling ~Fpipe, which is a race condition.
+            data->pipe.writeFd().reset();
+        }
         data->decStrong(this);
-        return -err;
-    }
+    }).detach();
 
     // Loop reading until either the timeout or the worker side is done (i.e. eof).
+    status_t err = NO_ERROR;
+    bool workerDone = false;
+    FdBuffer buffer;
     err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
     if (err != NO_ERROR) {
         ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err));
     }
 
-    // Done with the read fd. The worker thread closes the write one so
-    // we never race and get here first.
-    data->pipe.readFd().reset();
-
     // If the worker side is finished, then return its error (which may overwrite
     // our possible error -- but it's more interesting anyway). If not, then we timed out.
     {
-        unique_lock<mutex> lock(data->lock);
+        std::scoped_lock<std::mutex> lock(data->lock);
+        data->pipe.close();
         if (data->workerError != NO_ERROR) {
             err = data->workerError;
             ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err));
@@ -390,7 +356,6 @@
 CommandSection::~CommandSection() { free(mCommand); }
 
 status_t CommandSection::Execute(ReportWriter* writer) const {
-    FdBuffer buffer;
     Fpipe cmdPipe;
     Fpipe ihPipe;
 
@@ -411,6 +376,7 @@
     }
 
     cmdPipe.writeFd().reset();
+    FdBuffer buffer;
     status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs);
     writer->setSectionStats(buffer);
     if (readStatus != NO_ERROR || buffer.timedOut()) {
@@ -457,7 +423,7 @@
 
 DumpsysSection::~DumpsysSection() {}
 
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
     // checkService won't wait for the service to show up like getService will.
     sp<IBinder> service = defaultServiceManager()->checkService(mService);
 
@@ -466,19 +432,120 @@
         return NAME_NOT_FOUND;
     }
 
-    service->dump(pipeWriteFd, mArgs);
+    service->dump(pipeWriteFd.get(), mArgs);
 
     return NO_ERROR;
 }
 
 // ================================================================================
+TextDumpsysSection::TextDumpsysSection(int id, const char* service, ...)
+        :Section(id), mService(service) {
+    name = "dumpsys ";
+    name += service;
+
+    va_list args;
+    va_start(args, service);
+    while (true) {
+        const char* arg = va_arg(args, const char*);
+        if (arg == NULL) {
+            break;
+        }
+        mArgs.add(String16(arg));
+        name += " ";
+        name += arg;
+    }
+    va_end(args);
+}
+
+TextDumpsysSection::~TextDumpsysSection() {}
+
+status_t TextDumpsysSection::Execute(ReportWriter* writer) const {
+    // checkService won't wait for the service to show up like getService will.
+    sp<IBinder> service = defaultServiceManager()->checkService(mService);
+    if (service == NULL) {
+        ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string());
+        return NAME_NOT_FOUND;
+    }
+
+    // Create pipe
+    Fpipe dumpPipe;
+    if (!dumpPipe.init()) {
+        ALOGW("[%s] failed to setup pipe", this->name.string());
+        return -errno;
+    }
+
+    // Run dumping thread
+    const uint64_t start = Nanotime();
+    std::thread worker([write_fd = std::move(dumpPipe.writeFd()), service = std::move(service),
+                        this]() mutable {
+        // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+        signal(SIGPIPE, sigpipe_handler);
+        status_t err = service->dump(write_fd.get(), this->mArgs);
+        if (err != OK) {
+            ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
+        }
+        write_fd.reset();
+    });
+
+    // Collect dump content
+    FdBuffer buffer;
+    ProtoOutputStream proto;
+    proto.write(TextDumpProto::COMMAND, std::string(name.string()));
+    proto.write(TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start));
+    buffer.write(proto.data());
+
+    sp<EncodedBuffer> internalBuffer = buffer.data();
+    internalBuffer->writeHeader((uint32_t)TextDumpProto::CONTENT, WIRE_TYPE_LENGTH_DELIMITED);
+    size_t editPos = internalBuffer->wp()->pos();
+    internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size
+    size_t dataBeginPos = internalBuffer->wp()->pos();
+
+    status_t readStatus = buffer.read(dumpPipe.readFd(), this->timeoutMs);
+    dumpPipe.readFd().reset();
+    writer->setSectionStats(buffer);
+    if (readStatus != OK || buffer.timedOut()) {
+        ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.string(),
+              strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+        worker.detach();
+        return readStatus;
+    }
+    worker.join(); // wait for worker to finish
+
+    // Revisit the actual size from dumpsys and edit the internal buffer accordingly.
+    size_t dumpSize = buffer.size() - dataBeginPos;
+    internalBuffer->wp()->rewind()->move(editPos);
+    internalBuffer->writeRawVarint32(dumpSize);
+    internalBuffer->copy(dataBeginPos, dumpSize);
+
+    return writer->writeSection(buffer);
+}
+
+// ================================================================================
 // initialization only once in Section.cpp.
 map<log_id_t, log_time> LogSection::gLastLogsRetrieved;
 
-LogSection::LogSection(int id, log_id_t logID) : WorkerThreadSection(id), mLogID(logID) {
-    name = "logcat ";
-    name += android_log_id_to_name(logID);
-    switch (logID) {
+LogSection::LogSection(int id, const char* logID, ...) : WorkerThreadSection(id), mLogMode(logModeBase) {
+    name = "logcat -b ";
+    name += logID;
+
+    va_list args;
+    va_start(args, logID);
+    mLogID = android_name_to_log_id(logID);
+    while(true) {
+        const char* arg = va_arg(args, const char*);
+        if (arg == NULL) {
+            break;
+        }
+        if (!strcmp(arg, "-L")) {
+          // Read from last logcat buffer
+          mLogMode = mLogMode | ANDROID_LOG_PSTORE;
+        }
+        name += " ";
+        name += arg;
+    }
+    va_end(args);
+
+    switch (mLogID) {
         case LOG_ID_EVENTS:
         case LOG_ID_STATS:
         case LOG_ID_SECURITY:
@@ -507,42 +574,51 @@
     return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
 }
 
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
+    // heap profile shows that liblog malloc & free significant amount of memory in this process.
+    // Hence forking a new process to prevent memory fragmentation.
+    pid_t pid = fork();
+    if (pid < 0) {
+        ALOGW("[%s] failed to fork", this->name.string());
+        return errno;
+    }
+    if (pid > 0) {
+        return wait_child(pid, this->timeoutMs);
+    }
     // Open log buffer and getting logs since last retrieved time if any.
     unique_ptr<logger_list, void (*)(logger_list*)> loggers(
             gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
-                    ? android_logger_list_alloc(ANDROID_LOG_NONBLOCK, 0, 0)
-                    : android_logger_list_alloc_time(ANDROID_LOG_NONBLOCK,
-                                                     gLastLogsRetrieved[mLogID], 0),
+                    ? android_logger_list_alloc(mLogMode, 0, 0)
+                    : android_logger_list_alloc_time(mLogMode, gLastLogsRetrieved[mLogID], 0),
             android_logger_list_free);
 
     if (android_logger_open(loggers.get(), mLogID) == NULL) {
         ALOGE("[%s] Can't get logger.", this->name.string());
-        return -1;
+        _exit(EXIT_FAILURE);
     }
 
     log_msg msg;
     log_time lastTimestamp(0);
 
     ProtoOutputStream proto;
+    status_t err = OK;
     while (true) {  // keeps reading until logd buffer is fully read.
-        status_t err = android_logger_list_read(loggers.get(), &msg);
-        // err = 0 - no content, unexpected connection drop or EOF.
-        // err = +ive number - size of retrieved data from logger
-        // err = -ive number, OS supplied error _except_ for -EAGAIN
-        // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data.
-        if (err <= 0) {
-            if (err != -EAGAIN) {
+        status_t status = android_logger_list_read(loggers.get(), &msg);
+        // status = 0 - no content, unexpected connection drop or EOF.
+        // status = +ive number - size of retrieved data from logger
+        // status = -ive number, OS supplied error _except_ for -EAGAIN
+        // status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end.
+        if (status <= 0) {
+            if (status != -EAGAIN) {
                 ALOGW("[%s] fails to read a log_msg.\n", this->name.string());
+                err = -status;
             }
-            // dump previous logs and don't consider this error a failure.
             break;
         }
         if (mBinary) {
             // remove the first uint32 which is tag's index in event log tags
             android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t),
                                                                     msg.len() - sizeof(uint32_t));
-            ;
             android_log_list_element elem;
 
             lastTimestamp.tv_sec = msg.entry.sec;
@@ -602,9 +678,10 @@
             }
         } else {
             AndroidLogEntry entry;
-            err = android_log_processLogBuffer(&msg.entry, &entry);
-            if (err != NO_ERROR) {
+            status = android_log_processLogBuffer(&msg.entry, &entry);
+            if (status != OK) {
                 ALOGW("[%s] fails to process to an entry.\n", this->name.string());
+                err = status;
                 break;
             }
             lastTimestamp.tv_sec = entry.tv_sec;
@@ -623,17 +700,24 @@
                         trimTail(entry.message, entry.messageLen));
             proto.end(token);
         }
+        if (!proto.flush(pipeWriteFd.get())) {
+            if (errno == EPIPE) {
+                ALOGW("[%s] wrote to a broken pipe\n", this->name.string());
+            }
+            err = errno;
+            break;
+        }
+        proto.clear();
     }
     gLastLogsRetrieved[mLogID] = lastTimestamp;
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
-        ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
-        return EPIPE;
-    }
-    return NO_ERROR;
+    _exit(err);
 }
 
 // ================================================================================
 
+const int LINK_NAME_LEN = 64;
+const int EXE_NAME_LEN = 1024;
+
 TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeoutMs)
     : WorkerThreadSection(id, timeoutMs), mType(type) {
     name = "tombstone ";
@@ -642,7 +726,7 @@
 
 TombstoneSection::~TombstoneSection() {}
 
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
     std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
     if (proc.get() == nullptr) {
         ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -651,25 +735,37 @@
 
     const std::set<int> hal_pids = get_interesting_hal_pids();
 
-    ProtoOutputStream proto;
+    auto pooledBuffer = get_buffer_from_pool();
+    ProtoOutputStream proto(pooledBuffer);
+    // dumpBufferSize should be a multiple of page size (4 KB) to reduce memory fragmentation
+    size_t dumpBufferSize = 64 * 1024; // 64 KB is enough for most tombstone dump
+    char* dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE,
+                MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
     struct dirent* d;
+    char link_name[LINK_NAME_LEN];
+    char exe_name[EXE_NAME_LEN];
     status_t err = NO_ERROR;
     while ((d = readdir(proc.get()))) {
         int pid = atoi(d->d_name);
         if (pid <= 0) {
             continue;
         }
-
-        const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
-        std::string exe;
-        if (!android::base::Readlink(link_name, &exe)) {
-            ALOGE("Section %s: Can't read '%s': %s\n", name.string(),
-                    link_name.c_str(), strerror(errno));
+        snprintf(link_name, LINK_NAME_LEN, "/proc/%d/exe", pid);
+        struct stat fileStat;
+        if (stat(link_name, &fileStat) != OK) {
             continue;
         }
+        ssize_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN);
+        if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) {
+            ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno));
+            continue;
+        }
+        // readlink(2) does not put a null terminator at the end
+        exe_name[exe_name_len] = '\0';
 
         bool is_java_process;
-        if (exe == "/system/bin/app_process32" || exe == "/system/bin/app_process64") {
+        if (strncmp(exe_name, "/system/bin/app_process32", LINK_NAME_LEN) == 0 ||
+                strncmp(exe_name, "/system/bin/app_process64", LINK_NAME_LEN) == 0) {
             if (mType != "java") continue;
             // Don't bother dumping backtraces for the zygote.
             if (IsZygote(pid)) {
@@ -678,7 +774,7 @@
             }
 
             is_java_process = true;
-        } else if (should_dump_native_traces(exe.c_str())) {
+        } else if (should_dump_native_traces(exe_name)) {
             if (mType != "native") continue;
             is_java_process = false;
         } else if (hal_pids.find(pid) != hal_pids.end()) {
@@ -734,32 +830,58 @@
             ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus));
         }
 
-        auto dump = std::make_unique<char[]>(buffer.size());
+        // Resize dump buffer
+        if (dumpBufferSize < buffer.size()) {
+            munmap(dumpBuffer, dumpBufferSize);
+            while(dumpBufferSize < buffer.size()) dumpBufferSize = dumpBufferSize << 1;
+            dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE,
+                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+        }
         sp<ProtoReader> reader = buffer.data()->read();
         int i = 0;
         while (reader->hasNext()) {
-            dump[i] = reader->next();
+            dumpBuffer[i] = reader->next();
             i++;
         }
         uint64_t token = proto.start(android::os::BackTraceProto::TRACES);
         proto.write(android::os::BackTraceProto::Stack::PID, pid);
-        proto.write(android::os::BackTraceProto::Stack::DUMP, dump.get(), i);
+        proto.write(android::os::BackTraceProto::Stack::DUMP, dumpBuffer, i);
         proto.write(android::os::BackTraceProto::Stack::DUMP_DURATION_NS,
                     static_cast<long long>(Nanotime() - start));
         proto.end(token);
         dumpPipe.readFd().reset();
-    }
-
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
-        ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
-        if (err != NO_ERROR) {
-            return EPIPE;
+        if (!proto.flush(pipeWriteFd.get())) {
+            if (errno == EPIPE) {
+                ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
+            }
+            err = errno;
+            break;
         }
+        proto.clear();
     }
-
+    munmap(dumpBuffer, dumpBufferSize);
+    return_buffer_to_pool(pooledBuffer);
     return err;
 }
 
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback)
+    : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+    name = "registered ";
+    name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+    android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+    if(mCallback != nullptr) {
+        mCallback->onDumpSection(pfd);
+    }
+    return NO_ERROR;
+}
+
 }  // namespace incidentd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index f8649f8..698cc04 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,7 @@
 #include <stdarg.h>
 #include <map>
 
+#include <android/os/IIncidentDumpCallback.h>
 #include <log/log_read.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
@@ -90,7 +91,7 @@
 
     virtual status_t Execute(ReportWriter* writer) const;
 
-    virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
 };
 
 /**
@@ -111,14 +112,30 @@
 };
 
 /**
- * Section that calls dumpsys on a system service.
+ * Section that calls protobuf dumpsys on a system service, usually
+ * "dumpsys [service_name] --proto".
  */
 class DumpsysSection : public WorkerThreadSection {
 public:
     DumpsysSection(int id, const char* service, ...);
     virtual ~DumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+    String16 mService;
+    Vector<String16> mArgs;
+};
+
+/**
+ * Section that calls text dumpsys on a system service, usually "dumpsys [service_name]".
+ */
+class TextDumpsysSection : public Section {
+public:
+    TextDumpsysSection(int id, const char* service, ...);
+    virtual ~TextDumpsysSection();
+
+    virtual status_t Execute(ReportWriter* writer) const;
 
 private:
     String16 mService;
@@ -133,7 +150,7 @@
     SystemPropertyDumpsysSection(int id, const char* service, ...);
     virtual ~SystemPropertyDumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     String16 mService;
@@ -147,15 +164,19 @@
     // global last log retrieved timestamp for each log_id_t.
     static map<log_id_t, log_time> gLastLogsRetrieved;
 
+    // log mode: non blocking.
+    const static int logModeBase = ANDROID_LOG_NONBLOCK;
+
 public:
-    LogSection(int id, log_id_t logID);
+    LogSection(int id, const char* logID, ...);
     virtual ~LogSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     log_id_t mLogID;
     bool mBinary;
+    int mLogMode;
 };
 
 /**
@@ -166,12 +187,29 @@
     TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
     virtual ~TombstoneSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     std::string mType;
 };
 
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+    const uid_t uid;
+
+    BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback);
+    virtual ~BringYourOwnSection();
+
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+    const sp<IIncidentDumpCallback> mCallback;
+};
+
 
 /**
  * These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 9963533..1944d6e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -16,10 +16,10 @@
 
 #include "Log.h"
 
-#include "WorkDirectory.h"
-
+#include "incidentd_util.h"
 #include "proto_util.h"
 #include "PrivacyFilter.h"
+#include "WorkDirectory.h"
 
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <private/android_filesystem_config.h>
@@ -68,6 +68,9 @@
 /** metadata field id in IncidentProto */
 const int FIELD_ID_INCIDENT_METADATA = 2;
 
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
+
 /**
  * Read a protobuf from disk into the message.
  */
@@ -292,6 +295,7 @@
         report->set_cls(args.receiverCls());
         report->set_privacy_policy(args.getPrivacyPolicy());
         report->set_all_sections(args.all());
+        report->set_gzip(args.gzip());
         for (int section: args.sections()) {
             report->add_section(section);
         }
@@ -417,6 +421,24 @@
         return BAD_VALUE;
     }
 
+    pid_t zipPid = 0;
+    if (args.gzip()) {
+        Fpipe zipPipe;
+        if (!zipPipe.init()) {
+            ALOGE("[ReportFile] Failed to setup pipe for gzip");
+            close(writeFd);
+            return -errno;
+        }
+        int status = 0;
+        zipPid = fork_execute_cmd((char* const*)GZIP, zipPipe.readFd().release(), writeFd, &status);
+        close(writeFd);
+        if (zipPid < 0 || status != 0) {
+            ALOGE("[ReportFile] Failed to fork and exec gzip");
+            return status;
+        }
+        writeFd = zipPipe.writeFd().release();
+    }
+
     status_t err;
 
     for (const auto& report : mEnvelope.report()) {
@@ -437,6 +459,13 @@
     }
 
     close(writeFd);
+    if (zipPid > 0) {
+        status_t err = wait_child(zipPid, /* timeout_ms= */ 10 * 1000);
+        if (err != 0) {
+            ALOGE("[ReportFile] abnormal child process: %s", strerror(-err));
+        }
+        return err;
+    }
     return NO_ERROR;
 }
 
@@ -621,7 +650,7 @@
 
     map<string,WorkDirectoryEntry> files;
     get_directory_contents_locked(&files, 0);
-    
+
     for (map<string,WorkDirectoryEntry>::iterator it = files.begin();
             it != files.end(); it++) {
         sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs,
@@ -815,6 +844,7 @@
     out->setAll(report.all_sections());
     out->setReceiverPkg(report.pkg());
     out->setReceiverCls(report.cls());
+    out->setGzip(report.gzip());
 
     const int sectionCount = report.section_size();
     for (int i = 0; i < sectionCount; i++) {
diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp
index dfaf893..150ab99 100644
--- a/cmds/incidentd/src/incidentd_util.cpp
+++ b/cmds/incidentd/src/incidentd_util.cpp
@@ -18,6 +18,8 @@
 
 #include "incidentd_util.h"
 
+#include <android/util/EncodedBuffer.h>
+#include <fcntl.h>
 #include <sys/prctl.h>
 #include <wait.h>
 
@@ -27,8 +29,6 @@
 namespace os {
 namespace incidentd {
 
-using namespace android::base;
-
 const Privacy* get_privacy_of_section(int id) {
     int l = 0;
     int r = PRIVACY_POLICY_COUNT - 1;
@@ -47,6 +47,30 @@
     return NULL;
 }
 
+std::vector<sp<EncodedBuffer>> gBufferPool;
+std::mutex gBufferPoolLock;
+
+sp<EncodedBuffer> get_buffer_from_pool() {
+    std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+    if (gBufferPool.size() == 0) {
+        return new EncodedBuffer();
+    }
+    sp<EncodedBuffer> buffer = gBufferPool.back();
+    gBufferPool.pop_back();
+    return buffer;
+}
+
+void return_buffer_to_pool(sp<EncodedBuffer> buffer) {
+    buffer->clear();
+    std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+    gBufferPool.push_back(buffer);
+}
+
+void clear_buffer_pool() {
+    std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+    gBufferPool.clear();
+}
+
 // ================================================================================
 Fpipe::Fpipe() : mRead(), mWrite() {}
 
@@ -64,28 +88,52 @@
 
 unique_fd& Fpipe::writeFd() { return mWrite; }
 
-pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output) {
-    // fork used in multithreaded environment, avoid adding unnecessary code in child process
-    pid_t pid = fork();
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status) {
+    int in = -1;
+    if (input != nullptr) {
+        in = input->readFd().release();
+        // Auto close write end of the input pipe on exec to prevent leaking fd in child process
+        fcntl(input->writeFd().get(), F_SETFD, FD_CLOEXEC);
+    }
+    int out = output->writeFd().release();
+    // Auto close read end of the output pipe on exec
+    fcntl(output->readFd().get(), F_SETFD, FD_CLOEXEC);
+    return fork_execute_cmd(argv, in, out, status);
+}
+
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status) {
+    int dummy_status = 0;
+    if (status == nullptr) {
+        status = &dummy_status;
+    }
+    *status = 0;
+    pid_t pid = vfork();
+    if (pid < 0) {
+        *status = -errno;
+        return -1;
+    }
     if (pid == 0) {
-        if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 ||
-                              !input->close())) {
+        // In child
+        if (in >= 0 && (TEMP_FAILURE_RETRY(dup2(in, STDIN_FILENO)) < 0 || close(in))) {
             ALOGW("Failed to dup2 stdin.");
             _exit(EXIT_FAILURE);
         }
-        if (TEMP_FAILURE_RETRY(dup2(output->writeFd().get(), STDOUT_FILENO)) < 0 ||
-            !output->close()) {
+        if (TEMP_FAILURE_RETRY(dup2(out, STDOUT_FILENO)) < 0 || close(out)) {
             ALOGW("Failed to dup2 stdout.");
             _exit(EXIT_FAILURE);
         }
-        /* make sure the child dies when incidentd dies */
+        // Make sure the child dies when incidentd dies
         prctl(PR_SET_PDEATHSIG, SIGKILL);
         execvp(argv[0], argv);
         _exit(errno);  // always exits with failure if any
     }
-    // close the fds used in child process.
-    if (input != NULL) input->readFd().reset();
-    output->writeFd().reset();
+    // In parent
+    if ((in >= 0 && close(in) < 0) || close(out) < 0) {
+        ALOGW("Failed to close pd. Killing child process");
+        *status = -errno;
+        kill_child(pid);
+        return -1;
+    }
     return pid;
 }
 
@@ -120,9 +168,6 @@
 }
 
 // ================================================================================
-const int WAIT_MAX = 5;
-const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
-
 static status_t statusCode(int status) {
     if (WIFSIGNALED(status)) {
         VLOG("return by signal: %s", strerror(WTERMSIG(status)));
@@ -134,25 +179,64 @@
     return NO_ERROR;
 }
 
+static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
+    sigset_t child_mask, old_mask;
+    sigemptyset(&child_mask);
+    sigaddset(&child_mask, SIGCHLD);
+
+    if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
+        ALOGW("sigprocmask failed: %s", strerror(errno));
+        return false;
+    }
+
+    timespec ts;
+    ts.tv_sec = timeout_ms / 1000;
+    ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+    int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
+    int saved_errno = errno;
+
+    // Set the signals back the way they were.
+    if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
+        ALOGW("sigprocmask failed: %s", strerror(errno));
+        if (ret == 0) {
+            return false;
+        }
+    }
+    if (ret == -1) {
+        errno = saved_errno;
+        if (errno == EAGAIN) {
+            errno = ETIMEDOUT;
+        } else {
+            ALOGW("sigtimedwait failed: %s", strerror(errno));
+        }
+        return false;
+    }
+
+    pid_t child_pid = waitpid(pid, status, WNOHANG);
+    if (child_pid == pid) {
+        return true;
+    }
+    if (child_pid == -1) {
+        ALOGW("waitpid failed: %s", strerror(errno));
+    } else {
+        ALOGW("Waiting for pid %d, got pid %d instead", pid, child_pid);
+    }
+    return false;
+}
+
 status_t kill_child(pid_t pid) {
     int status;
-    VLOG("try to kill child process %d", pid);
     kill(pid, SIGKILL);
     if (waitpid(pid, &status, 0) == -1) return -1;
     return statusCode(status);
 }
 
-status_t wait_child(pid_t pid) {
+status_t wait_child(pid_t pid, int timeout_ms) {
     int status;
-    bool died = false;
-    // wait for child to report status up to 1 seconds
-    for (int loop = 0; !died && loop < WAIT_MAX; loop++) {
-        if (waitpid(pid, &status, WNOHANG) == pid) died = true;
-        // sleep for 0.2 second
-        nanosleep(&WAIT_INTERVAL_NS, NULL);
+    if (waitpid_with_timeout(pid, timeout_ms, &status)) {
+        return statusCode(status);
     }
-    if (!died) return kill_child(pid);
-    return statusCode(status);
+    return kill_child(pid);
 }
 
 }  // namespace incidentd
diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h
index cc30768..8499889 100644
--- a/cmds/incidentd/src/incidentd_util.h
+++ b/cmds/incidentd/src/incidentd_util.h
@@ -19,18 +19,21 @@
 #define INCIDENTD_UTIL_H
 
 #include <stdarg.h>
-#include <unistd.h>
-
-#include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 
 #include "Privacy.h"
 
 namespace android {
+
+namespace util {
+class EncodedBuffer;
+}
+
 namespace os {
 namespace incidentd {
 
-using namespace android::base;
+using android::base::unique_fd;
+using android::util::EncodedBuffer;
 
 /**
  * Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST;
@@ -38,6 +41,25 @@
 const Privacy* get_privacy_of_section(int id);
 
 /**
+ * Get an EncodedBuffer from an internal pool, or create and return a new one if the pool is empty.
+ * The EncodedBuffer should be returned after use.
+ * Thread safe.
+ */
+sp<EncodedBuffer> get_buffer_from_pool();
+
+/**
+ * Return the EncodedBuffer back to the pool for reuse.
+ * Thread safe.
+ */
+void return_buffer_to_pool(sp<EncodedBuffer> buffer);
+
+/**
+ * Clear the buffer pool to free memory, after taking an incident report.
+ * Thread safe.
+ */
+void clear_buffer_pool();
+
+/**
  * This class wraps android::base::Pipe.
  */
 class Fpipe {
@@ -56,11 +78,24 @@
 };
 
 /**
- * Forks and exec a command with two pipes, one connects stdin for input,
- * one connects stdout for output. It returns the pid of the child.
- * Input pipe can be NULL to indicate child process doesn't read stdin.
+ * Forks and exec a command with two pipes and returns the pid of the child, or -1 when it fails.
+ *
+ * input connects stdin for input. output connects stdout for output. input can be nullptr to
+ * indicate that child process doesn't read stdin. This function will close in and out fds upon
+ * success. If status is not NULL, the status information will be stored in the int to which it
+ * points.
  */
-pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output);
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status = nullptr);
+
+/**
+ * Forks and exec a command that reads from in fd and writes to out fd and returns the pid of the
+ * child, or -1 when it fails.
+ *
+ * in can be -1 to indicate that child process doesn't read stdin. This function will close in and
+ * out fds upon success. If status is not NULL, the status information will be stored in the int
+ * to which it points.
+ */
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status = nullptr);
 
 /**
  * Grabs varargs from stack and stores them in heap with NULL-terminated array.
@@ -76,7 +111,7 @@
  * Methods to wait or kill child process, return exit status code.
  */
 status_t kill_child(pid_t pid);
-status_t wait_child(pid_t pid);
+status_t wait_child(pid_t pid, int timeout_ms = 1000);
 
 status_t start_detached_thread(const function<void ()>& func);
 
diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto
index 7563da2..85fd2da 100644
--- a/cmds/incidentd/src/report_file.proto
+++ b/cmds/incidentd/src/report_file.proto
@@ -65,6 +65,11 @@
          * the given client.
          */
         optional bool share_approved = 8;
+
+        /**
+         * Whether the report is gzipped.
+         */
+        optional bool gzip = 9;
     }
 
     /**
diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java
index a077745..08216d9 100644
--- a/cmds/input/src/com/android/commands/input/Input.java
+++ b/cmds/input/src/com/android/commands/input/Input.java
@@ -430,6 +430,6 @@
                 + " (Default: touchscreen)");
         out.println("      press (Default: trackball)");
         out.println("      roll <dx> <dy> (Default: trackball)");
-        out.println("      event <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)");
+        out.println("      motionevent <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)");
     }
 }
diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING
new file mode 100644
index 0000000..56f5cc0
--- /dev/null
+++ b/cmds/locksettings/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit-devicepolicy": [
+        {
+            "name": "CtsDevicePolicyManagerTestCases",
+            "options": [
+                {
+                    "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
+                },
+                {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                }
+            ]
+        }
+    ]
+}
diff --git a/cmds/media/Android.bp b/cmds/media/Android.bp
deleted file mode 100644
index 7879c53..0000000
--- a/cmds/media/Android.bp
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2013 The Android Open Source Project
-//
-
-java_binary {
-    name: "media",
-    wrapper: "media",
-    srcs: ["**/*.java"],
-}
diff --git a/cmds/media/MODULE_LICENSE_APACHE2 b/cmds/media/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/cmds/media/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/cmds/media/NOTICE b/cmds/media/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/cmds/media/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-2008, 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.
-
-   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.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/cmds/media/media b/cmds/media/media
deleted file mode 100755
index 00c3915..0000000
--- a/cmds/media/media
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/system/bin/sh
-export CLASSPATH=/system/framework/media.jar
-exec app_process /system/bin com.android.commands.media.Media "$@"
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
deleted file mode 100644
index 1e915f8..0000000
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
-**
-** Copyright 2013, 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.
-*/
-
-package com.android.commands.media;
-
-import android.app.ActivityThread;
-import android.content.Context;
-import android.media.MediaMetadata;
-import android.media.session.ISessionManager;
-import android.media.session.MediaController;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.QueueItem;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.AndroidException;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-
-import com.android.internal.os.BaseCommand;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.util.List;
-
-public class Media extends BaseCommand {
-    // This doesn't belongs to any package. Setting the package name to empty string.
-    private static final String PACKAGE_NAME = "";
-    private static ActivityThread sThread;
-    private static MediaSessionManager sMediaSessionManager;
-    private ISessionManager mSessionService;
-
-    /**
-     * Command-line entry point.
-     *
-     * @param args The command-line arguments
-     */
-    public static void main(String[] args) {
-        (new Media()).run(args);
-    }
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println(
-                "usage: media [subcommand] [options]\n" +
-                "       media dispatch KEY\n" +
-                "       media list-sessions\n" +
-                "       media monitor <tag>\n" +
-                "       media volume [options]\n" +
-                "\n" +
-                "media dispatch: dispatch a media key to the system.\n" +
-                "                KEY may be: play, pause, play-pause, mute, headsethook,\n" +
-                "                stop, next, previous, rewind, record, fast-forword.\n" +
-                "media list-sessions: print a list of the current sessions.\n" +
-                        "media monitor: monitor updates to the specified session.\n" +
-                "                       Use the tag from list-sessions.\n" +
-                "media volume:  " + VolumeCtrl.USAGE
-        );
-    }
-
-    @Override
-    public void onRun() throws Exception {
-        if (sThread == null) {
-            Looper.prepareMainLooper();
-            sThread = ActivityThread.systemMain();
-            Context context = sThread.getSystemContext();
-            sMediaSessionManager =
-                    (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        }
-        mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
-                Context.MEDIA_SESSION_SERVICE));
-        if (mSessionService == null) {
-            System.err.println(NO_SYSTEM_ERROR_CODE);
-            throw new AndroidException(
-                    "Can't connect to media session service; is the system running?");
-        }
-
-        String op = nextArgRequired();
-
-        if (op.equals("dispatch")) {
-            runDispatch();
-        } else if (op.equals("list-sessions")) {
-            runListSessions();
-        } else if (op.equals("monitor")) {
-            runMonitor();
-        } else if (op.equals("volume")) {
-            runVolume();
-        } else {
-            showError("Error: unknown command '" + op + "'");
-            return;
-        }
-    }
-
-    private void sendMediaKey(KeyEvent event) {
-        try {
-            mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private void runMonitor() throws Exception {
-        String id = nextArgRequired();
-        if (id == null) {
-            showError("Error: must include a session id");
-            return;
-        }
-
-        boolean success = false;
-        try {
-            List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
-            for (MediaController controller : controllers) {
-                try {
-                    if (controller != null && id.equals(controller.getTag())) {
-                        ControllerMonitor monitor = new ControllerMonitor(controller);
-                        monitor.run();
-                        success = true;
-                        break;
-                    }
-                } catch (RemoteException e) {
-                    // ignore
-                }
-            }
-        } catch (Exception e) {
-            System.out.println("***Error monitoring session*** " + e.getMessage());
-        }
-        if (!success) {
-            System.out.println("No session found with id " + id);
-        }
-    }
-
-    private void runDispatch() throws Exception {
-        String cmd = nextArgRequired();
-        int keycode;
-        if ("play".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
-        } else if ("pause".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_PAUSE;
-        } else if ("play-pause".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
-        } else if ("mute".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MUTE;
-        } else if ("headsethook".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_HEADSETHOOK;
-        } else if ("stop".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_STOP;
-        } else if ("next".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_NEXT;
-        } else if ("previous".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
-        } else if ("rewind".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_REWIND;
-        } else if ("record".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_RECORD;
-        } else if ("fast-forward".equals(cmd)) {
-            keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
-        } else {
-            showError("Error: unknown dispatch code '" + cmd + "'");
-            return;
-        }
-        final long now = SystemClock.uptimeMillis();
-        sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0,
-                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
-        sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0,
-                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
-    }
-
-    class ControllerCallback extends MediaController.Callback {
-        @Override
-        public void onSessionDestroyed() {
-            System.out.println("onSessionDestroyed. Enter q to quit.");
-        }
-
-        @Override
-        public void onSessionEvent(String event, Bundle extras) {
-            System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
-        }
-
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            System.out.println("onPlaybackStateChanged " + state);
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            String mmString = metadata == null ? null : "title=" + metadata
-                    .getDescription();
-            System.out.println("onMetadataChanged " + mmString);
-        }
-
-        @Override
-        public void onQueueChanged(List<QueueItem> queue) {
-            System.out.println("onQueueChanged, "
-                    + (queue == null ? "null queue" : " size=" + queue.size()));
-        }
-
-        @Override
-        public void onQueueTitleChanged(CharSequence title) {
-            System.out.println("onQueueTitleChange " + title);
-        }
-
-        @Override
-        public void onExtrasChanged(Bundle extras) {
-            System.out.println("onExtrasChanged " + extras);
-        }
-
-        @Override
-        public void onAudioInfoChanged(PlaybackInfo info) {
-            System.out.println("onAudioInfoChanged " + info);
-        }
-    }
-
-    private class ControllerMonitor {
-        private final MediaController mController;
-        private final ControllerCallback mControllerCallback;
-
-        ControllerMonitor(MediaController controller) {
-            mController = controller;
-            mControllerCallback = new ControllerCallback();
-        }
-
-        void printUsageMessage() {
-            try {
-                System.out.println("V2Monitoring session " + mController.getTag()
-                        + "...  available commands: play, pause, next, previous");
-            } catch (RuntimeException e) {
-                System.out.println("Error trying to monitor session!");
-            }
-            System.out.println("(q)uit: finish monitoring");
-        }
-
-        void run() throws RemoteException {
-            printUsageMessage();
-            HandlerThread cbThread = new HandlerThread("MediaCb") {
-                @Override
-                protected void onLooperPrepared() {
-                    try {
-                        mController.registerCallback(mControllerCallback);
-                    } catch (RuntimeException e) {
-                        System.out.println("Error registering monitor callback");
-                    }
-                }
-            };
-            cbThread.start();
-
-            try {
-                InputStreamReader converter = new InputStreamReader(System.in);
-                BufferedReader in = new BufferedReader(converter);
-                String line;
-
-                while ((line = in.readLine()) != null) {
-                    boolean addNewline = true;
-                    if (line.length() <= 0) {
-                        addNewline = false;
-                    } else if ("q".equals(line) || "quit".equals(line)) {
-                        break;
-                    } else if ("play".equals(line)) {
-                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
-                    } else if ("pause".equals(line)) {
-                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
-                    } else if ("next".equals(line)) {
-                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
-                    } else if ("previous".equals(line)) {
-                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
-                    } else {
-                        System.out.println("Invalid command: " + line);
-                    }
-
-                    synchronized (this) {
-                        if (addNewline) {
-                            System.out.println("");
-                        }
-                        printUsageMessage();
-                    }
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                cbThread.getLooper().quit();
-                try {
-                    mController.unregisterCallback(mControllerCallback);
-                } catch (Exception e) {
-                    // ignoring
-                }
-            }
-        }
-
-        private void dispatchKeyCode(int keyCode) {
-            final long now = SystemClock.uptimeMillis();
-            KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
-            KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
-            try {
-                mController.dispatchMediaButtonEvent(down);
-                mController.dispatchMediaButtonEvent(up);
-            } catch (RuntimeException e) {
-                System.out.println("Failed to dispatch " + keyCode);
-            }
-        }
-    }
-
-    private void runListSessions() {
-        System.out.println("Sessions:");
-        try {
-            List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
-            for (MediaController controller : controllers) {
-                if (controller != null) {
-                    try {
-                        System.out.println("  tag=" + controller.getTag()
-                                + ", package=" + controller.getPackageName());
-                    } catch (RuntimeException e) {
-                        // ignore
-                    }
-                }
-            }
-        } catch (Exception e) {
-            System.out.println("***Error listing sessions***");
-        }
-    }
-
-    //=================================
-    // "volume" command for stream volume control
-    private void runVolume() throws Exception {
-        VolumeCtrl.run(this);
-    }
-}
diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java
deleted file mode 100755
index 1629c6f..0000000
--- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
-**
-** Copyright 2016, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package com.android.commands.media;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.AudioSystem;
-import android.media.IAudioService;
-import android.os.ServiceManager;
-import android.util.AndroidException;
-
-import com.android.internal.os.BaseCommand;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.lang.ArrayIndexOutOfBoundsException;
-
-/**
- * Command line tool to exercise AudioService.setStreamVolume()
- *                           and AudioService.adjustStreamVolume()
- */
-public class VolumeCtrl {
-
-    private final static String TAG = "VolumeCtrl";
-
-    // --stream affects --set, --adj or --get options.
-    // --show affects --set and --adj options.
-    // --get can be used with --set, --adj or by itself.
-    public final static String USAGE = new String(
-            "the options are as follows: \n" +
-            "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" +
-            "\t\t                controls AudioManager.STREAM_MUSIC if no stream is specified\n"+
-            "\t\t--set INDEX     sets the volume index value\n" +
-            "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n" +
-            "\t\t--get           outputs the current volume\n" +
-            "\t\t--show          shows the UI during the volume change\n" +
-            "\texamples:\n" +
-            "\t\tadb shell media volume --show --stream 3 --set 11\n" +
-            "\t\tadb shell media volume --stream 0 --adj lower\n" +
-            "\t\tadb shell media volume --stream 3 --get\n"
-            );
-
-    private final static int VOLUME_CONTROL_MODE_SET = 1;
-    private final static int VOLUME_CONTROL_MODE_ADJUST = 2;
-
-    private final static String ADJUST_LOWER = "lower";
-    private final static String ADJUST_SAME = "same";
-    private final static String ADJUST_RAISE = "raise";
-
-    public static void run(BaseCommand cmd) throws Exception {
-        //----------------------------------------
-        // Default parameters
-        int stream = AudioManager.STREAM_MUSIC;
-        int volIndex = 5;
-        int mode = 0;
-        int adjDir = AudioManager.ADJUST_RAISE;
-        boolean showUi = false;
-        boolean doGet = false;
-
-        //----------------------------------------
-        // read options
-        String option;
-        String adjustment = null;
-        while ((option = cmd.nextOption()) != null) {
-            switch (option) {
-                case "--show":
-                    showUi = true;
-                    break;
-                case "--get":
-                    doGet = true;
-                    log(LOG_V, "will get volume");
-                    break;
-                case "--stream":
-                    stream = Integer.decode(cmd.nextArgRequired()).intValue();
-                    log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")");
-                    break;
-                case "--set":
-                    volIndex = Integer.decode(cmd.nextArgRequired()).intValue();
-                    mode = VOLUME_CONTROL_MODE_SET;
-                    log(LOG_V, "will set volume to index=" + volIndex);
-                    break;
-                case "--adj":
-                    mode = VOLUME_CONTROL_MODE_ADJUST;
-                    adjustment = cmd.nextArgRequired();
-                    log(LOG_V, "will adjust volume");
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown argument " + option);
-            }
-        }
-
-        //------------------------------
-        // Read options: validation
-        if (mode == VOLUME_CONTROL_MODE_ADJUST) {
-            if (adjustment == null) {
-                cmd.showError("Error: no valid volume adjustment (null)");
-                return;
-            }
-            switch (adjustment) {
-                case ADJUST_RAISE: adjDir = AudioManager.ADJUST_RAISE; break;
-                case ADJUST_SAME: adjDir = AudioManager.ADJUST_SAME; break;
-                case ADJUST_LOWER: adjDir = AudioManager.ADJUST_LOWER; break;
-                default:
-                    cmd.showError("Error: no valid volume adjustment, was " + adjustment
-                            + ", expected " + ADJUST_LOWER + "|" + ADJUST_SAME + "|"
-                            + ADJUST_RAISE);
-                    return;
-            }
-        }
-
-        //----------------------------------------
-        // Test initialization
-        log(LOG_V, "Connecting to AudioService");
-        IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService(
-                Context.AUDIO_SERVICE));
-        if (audioService == null) {
-            System.err.println(BaseCommand.NO_SYSTEM_ERROR_CODE);
-            throw new AndroidException(
-                    "Can't connect to audio service; is the system running?");
-        }
-
-        if (mode == VOLUME_CONTROL_MODE_SET) {
-            if ((volIndex > audioService.getStreamMaxVolume(stream))
-                    || (volIndex < audioService.getStreamMinVolume(stream))) {
-                cmd.showError(String.format("Error: invalid volume index %d for stream %d "
-                        + "(should be in [%d..%d])", volIndex, stream,
-                        audioService.getStreamMinVolume(stream),
-                        audioService.getStreamMaxVolume(stream)));
-                return;
-            }
-        }
-
-        //----------------------------------------
-        // Non-interactive test
-        final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0;
-        final String pack = cmd.getClass().getPackage().getName();
-        if (mode == VOLUME_CONTROL_MODE_SET) {
-            audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/);
-        } else if (mode == VOLUME_CONTROL_MODE_ADJUST) {
-            audioService.adjustStreamVolume(stream, adjDir, flag, pack);
-        }
-        if (doGet) {
-            log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
-                       " in range [" + audioService.getStreamMinVolume(stream) +
-                       ".." + audioService.getStreamMaxVolume(stream) + "]");
-        }
-    }
-
-    //--------------------------------------------
-    // Utilities
-
-    static final String LOG_V = "[v]";
-    static final String LOG_W = "[w]";
-    static final String LOG_OK = "[ok]";
-
-    static void log(String code, String msg) {
-        System.out.println(code + " " + msg);
-    }
-
-    static String streamName(int stream) {
-        try {
-            return AudioSystem.STREAM_NAMES[stream];
-        } catch (ArrayIndexOutOfBoundsException e) {
-            return "invalid stream";
-        }
-    }
-
-}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 4410f1c..bb32dd2 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -105,7 +105,7 @@
     char *cmd[] = {
         (char*) "am",
         (char*) "broadcast",
-        (char*) "am",
+        (char*) "-a",
         (char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
         (char*) "-d",
         &filePath[0],
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index e494ea3..0617eb6 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -44,19 +44,9 @@
 
 cc_defaults {
     name: "statsd_defaults",
-    aidl: {
-        include_dirs: ["frameworks/base/core/java"],
-    },
 
     srcs: [
-        ":statsd_aidl",
-        ":ICarStatsService.aidl",
         "src/active_config_list.proto",
-        "src/statsd_config.proto",
-        "src/uid_data.proto",
-        "src/FieldValue.cpp",
-        "src/hash.cpp",
-        "src/stats_log_util.cpp",
         "src/anomaly/AlarmMonitor.cpp",
         "src/anomaly/AlarmTracker.cpp",
         "src/anomaly/AnomalyTracker.cpp",
@@ -64,52 +54,57 @@
         "src/anomaly/subscriber_util.cpp",
         "src/condition/CombinationConditionTracker.cpp",
         "src/condition/condition_util.cpp",
-        "src/condition/SimpleConditionTracker.cpp",
         "src/condition/ConditionWizard.cpp",
-        "src/condition/StateTracker.cpp",
+        "src/condition/SimpleConditionTracker.cpp",
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
-        "src/external/CarStatsPuller.cpp",
-        "src/external/GpuStatsPuller.cpp",
+        "src/experiment_ids.proto",
         "src/external/Perfetto.cpp",
-        "src/external/StatsPuller.cpp",
-        "src/external/StatsCallbackPuller.cpp",
-        "src/external/StatsCompanionServicePuller.cpp",
-        "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/PowerStatsPuller.cpp",
-        "src/external/ResourceHealthManagerPuller.cpp",
-        "src/external/TrainInfoPuller.cpp",
-        "src/external/StatsPullerManager.cpp",
+        "src/external/PullResultReceiver.cpp",
         "src/external/puller_util.cpp",
+        "src/external/StatsCallbackPuller.cpp",
+        "src/external/StatsPuller.cpp",
+        "src/external/StatsPullerManager.cpp",
+        "src/external/TrainInfoPuller.cpp",
+        "src/FieldValue.cpp",
+        "src/guardrail/StatsdStats.cpp",
+        "src/hash.cpp",
+        "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
         "src/matchers/CombinationLogMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
         "src/matchers/SimpleLogMatchingTracker.cpp",
-        "src/metrics/MetricProducer.cpp",
-        "src/metrics/EventMetricProducer.cpp",
+        "src/metadata_util.cpp",
         "src/metrics/CountMetricProducer.cpp",
-        "src/metrics/DurationMetricProducer.cpp",
-        "src/metrics/duration_helper/OringDurationTracker.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
-        "src/metrics/ValueMetricProducer.cpp",
+        "src/metrics/duration_helper/OringDurationTracker.cpp",
+        "src/metrics/DurationMetricProducer.cpp",
+        "src/metrics/EventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
-        "src/metrics/MetricsManager.cpp",
+        "src/metrics/MetricProducer.cpp",
         "src/metrics/metrics_manager_util.cpp",
+        "src/metrics/MetricsManager.cpp",
+        "src/metrics/ValueMetricProducer.cpp",
         "src/packages/UidMap.cpp",
-        "src/storage/StorageManager.cpp",
+        "src/shell/shell_config.proto",
+        "src/shell/ShellSubscriber.cpp",
+        "src/socket/StatsSocketListener.cpp",
+        "src/state/StateManager.cpp",
+        "src/state/StateTracker.cpp",
+        "src/stats_log_util.cpp",
+        "src/statscompanion_util.cpp",
+        "src/statsd_config.proto",
+        "src/statsd_metadata.proto",
         "src/StatsLogProcessor.cpp",
         "src/StatsService.cpp",
-        "src/statscompanion_util.cpp",
+        "src/storage/StorageManager.cpp",
         "src/subscriber/IncidentdReporter.cpp",
         "src/subscriber/SubscriberReporter.cpp",
-        "src/HashableDimensionKey.cpp",
-        "src/guardrail/StatsdStats.cpp",
-        "src/socket/StatsSocketListener.cpp",
-        "src/shell/ShellSubscriber.cpp",
-        "src/shell/shell_config.proto",
+        "src/uid_data.proto",
+        "src/utils/MultiConditionTrigger.cpp",
     ],
 
     local_include_dirs: [
@@ -117,79 +112,82 @@
     ],
 
     static_libs: [
-        "libhealthhalutils",
-    ],
-
-    shared_libs: [
         "libbase",
-        "libbinder",
-        "libgraphicsenv",
+        "libcutils",
+        "libgtest_prod",
+        "libprotoutil",
+        "libstatslog_statsd",
+        "libsysutils",
+        "libutils",
+        "statsd-aidl-ndk_platform",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
         "libincident",
         "liblog",
-        "libutils",
-        "libservices",
-        "libprotoutil",
-        "libstatslog",
-        "libhardware",
-        "libhardware_legacy",
-        "libhidlbase",
-        "android.frameworks.stats@1.0",
-        "android.hardware.health@2.0",
-        "android.hardware.power@1.0",
-        "android.hardware.power@1.1",
-        "android.hardware.power.stats@1.0",
-        "libpackagelistparser",
-        "libstatsmetadata",
-        "libsysutils",
-        "libcutils",
-    ],
-}
-
-// ================
-// libstatsmetadata
-// ================
-
-genrule {
-    name: "atoms_info.h",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h",
-    out: [
-        "atoms_info.h",
     ],
 }
 
 genrule {
-    name: "atoms_info.cpp",
+    name: "statslog_statsd.h",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp",
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util",
     out: [
-        "atoms_info.cpp",
+        "statslog_statsd.h",
     ],
 }
 
-cc_library_shared {
-    name: "libstatsmetadata",
-    host_supported: true,
-    generated_sources: [
-        "atoms_info.cpp",
+genrule {
+    name: "statslog_statsd.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h",
+    out: [
+        "statslog_statsd.cpp",
     ],
-    generated_headers: [
-        "atoms_info.h",
+}
+
+genrule {
+    name: "statslog_statsdtest.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util",
+    out: [
+        "statslog_statsdtest.h",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
+}
+
+genrule {
+    name: "statslog_statsdtest.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h",
+    out: [
+        "statslog_statsdtest.cpp",
     ],
-    export_generated_headers: [
-        "atoms_info.h",
+}
+
+cc_library_static {
+    name: "libstatslog_statsdtest",
+    generated_sources: ["statslog_statsdtest.cpp"],
+    generated_headers: ["statslog_statsdtest.h"],
+    export_generated_headers: ["statslog_statsdtest.h"],
+    shared_libs: [
+        "libstatssocket",
+    ]
+}
+
+cc_library_static {
+    name: "libstatslog_statsd",
+    generated_sources: ["statslog_statsd.cpp"],
+    generated_headers: ["statslog_statsd.h"],
+    export_generated_headers: ["statslog_statsd.h"],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
     ],
     shared_libs: [
-        "libcutils",
-        "libstatslog",
-    ],
+        "libstatssocket",
+    ]
 }
 
-
 // =========
 // statsd
 // =========
@@ -226,11 +224,18 @@
 
     proto: {
         type: "lite",
+        static: true,
     },
+    stl: "libc++_static",
 
-    shared_libs: ["libgtest_prod"],
+    shared_libs: [
+        "libstatssocket",
+    ],
 
-    init_rc: ["statsd.rc"],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 // ==============
@@ -240,7 +245,20 @@
 cc_test {
     name: "statsd_test",
     defaults: ["statsd_defaults"],
-    test_suites: ["device-tests"],
+    test_suites: ["device-tests", "mts"],
+    test_config: "statsd_test.xml",
+
+    //TODO(b/153588990): Remove when the build system properly separates
+    //32bit and 64bit architectures.
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        },
+        lib32: {
+            suffix: "32",
+        },
+    },
 
     cflags: [
         "-Wall",
@@ -251,6 +269,8 @@
         "-Wno-unused-parameter",
     ],
 
+    require_root: true,
+
     srcs: [
         // atom_field_options.proto needs field_options.proto, but that is
         // not included in libprotobuf-cpp-lite, so compile it here.
@@ -258,62 +278,65 @@
 
         "src/atom_field_options.proto",
         "src/atoms.proto",
-        "src/stats_log.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
         "tests/AlarmMonitor_test.cpp",
         "tests/anomaly/AlarmTracker_test.cpp",
         "tests/anomaly/AnomalyTracker_test.cpp",
+        "tests/condition/CombinationConditionTracker_test.cpp",
+        "tests/condition/ConditionTimer_test.cpp",
+        "tests/condition/SimpleConditionTracker_test.cpp",
         "tests/ConfigManager_test.cpp",
+        "tests/e2e/Alarm_e2e_test.cpp",
+        "tests/e2e/Anomaly_count_e2e_test.cpp",
+        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
+        "tests/e2e/Attribution_e2e_test.cpp",
+        "tests/e2e/ConfigTtl_e2e_test.cpp",
+        "tests/e2e/CountMetric_e2e_test.cpp",
+        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
+        "tests/e2e/MetricActivation_e2e_test.cpp",
+        "tests/e2e/MetricConditionLink_e2e_test.cpp",
+        "tests/e2e/PartialBucket_e2e_test.cpp",
+        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
+        "tests/e2e/WakelockDuration_e2e_test.cpp",
         "tests/external/puller_util_test.cpp",
-        "tests/external/GpuStatsPuller_test.cpp",
-        "tests/external/IncidentReportArgs_test.cpp",
+        "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
+        "tests/external/StatsPullerManager_test.cpp",
+        "tests/FieldValue_test.cpp",
+        "tests/guardrail/StatsdStats_test.cpp",
+        "tests/HashableDimensionKey_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
+        "tests/log_event/LogEventQueue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
         "tests/LogEvent_test.cpp",
-        "tests/log_event/LogEventQueue_test.cpp",
-        "tests/MetricsManager_test.cpp",
-        "tests/StatsLogProcessor_test.cpp",
-        "tests/StatsService_test.cpp",
-        "tests/UidMap_test.cpp",
-        "tests/FieldValue_test.cpp",
-        "tests/condition/CombinationConditionTracker_test.cpp",
-        "tests/condition/SimpleConditionTracker_test.cpp",
-        "tests/condition/StateTracker_test.cpp",
-        "tests/condition/ConditionTimer_test.cpp",
-        "tests/metrics/OringDurationTracker_test.cpp",
-        "tests/metrics/MaxDurationTracker_test.cpp",
+        "tests/metadata_util_test.cpp",
         "tests/metrics/CountMetricProducer_test.cpp",
         "tests/metrics/DurationMetricProducer_test.cpp",
         "tests/metrics/EventMetricProducer_test.cpp",
-        "tests/metrics/ValueMetricProducer_test.cpp",
         "tests/metrics/GaugeMetricProducer_test.cpp",
-        "tests/guardrail/StatsdStats_test.cpp",
+        "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/metrics_test_helper.cpp",
-        "tests/statsd_test_util.cpp",
-        "tests/storage/StorageManager_test.cpp",
-        "tests/e2e/WakelockDuration_e2e_test.cpp",
-        "tests/e2e/MetricActivation_e2e_test.cpp",
-        "tests/e2e/MetricConditionLink_e2e_test.cpp",
-        "tests/e2e/Alarm_e2e_test.cpp",
-        "tests/e2e/Attribution_e2e_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
-        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
-        "tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp",
-        "tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp",
-        "tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp",
-        "tests/e2e/Anomaly_count_e2e_test.cpp",
-        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
-        "tests/e2e/ConfigTtl_e2e_test.cpp",
-        "tests/e2e/PartialBucket_e2e_test.cpp",
-        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/metrics/OringDurationTracker_test.cpp",
+        "tests/metrics/ValueMetricProducer_test.cpp",
+        "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
+        "tests/state/StateTracker_test.cpp",
+        "tests/statsd_test_util.cpp",
+        "tests/StatsLogProcessor_test.cpp",
+        "tests/StatsService_test.cpp",
+        "tests/storage/StorageManager_test.cpp",
+        "tests/UidMap_test.cpp",
+        "tests/utils/MultiConditionTrigger_test.cpp",
     ],
 
     static_libs: [
         "libgmock",
         "libplatformprotos",
+        "libstatslog_statsdtest",
+        "libstatssocket_private",
     ],
 
     proto: {
@@ -321,8 +344,6 @@
         include_dirs: ["external/protobuf/src"],
     },
 
-    shared_libs: ["libprotobuf-cpp-lite"],
-
 }
 
 //#############################
@@ -338,17 +359,17 @@
         // not included in libprotobuf-cpp-lite, so compile it here.
         ":libprotobuf-internal-protos",
 
+        "benchmark/duration_metric_benchmark.cpp",
+        "benchmark/filter_value_benchmark.cpp",
+        "benchmark/get_dimensions_for_condition_benchmark.cpp",
+        "benchmark/hello_world_benchmark.cpp",
+        "benchmark/log_event_benchmark.cpp",
+        "benchmark/main.cpp",
+        "benchmark/metric_util.cpp",
+        "benchmark/stats_write_benchmark.cpp",
         "src/atom_field_options.proto",
         "src/atoms.proto",
         "src/stats_log.proto",
-        "benchmark/main.cpp",
-        "benchmark/hello_world_benchmark.cpp",
-        "benchmark/log_event_benchmark.cpp",
-        "benchmark/stats_write_benchmark.cpp",
-        "benchmark/filter_value_benchmark.cpp",
-        "benchmark/get_dimensions_for_condition_benchmark.cpp",
-        "benchmark/metric_util.cpp",
-        "benchmark/duration_metric_benchmark.cpp",
     ],
 
     proto: {
@@ -364,17 +385,18 @@
         "-Wno-unused-function",
 
         // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-        "-Wno-varargs"
+        "-Wno-varargs",
     ],
 
     static_libs: [
         "libplatformprotos",
+        "libstatssocket_private",
     ],
 
     shared_libs: [
         "libgtest_prod",
-        "libstatslog",
         "libprotobuf-cpp-lite",
+        "libstatslog",
     ],
 }
 
@@ -388,11 +410,11 @@
     },
 
     srcs: [
-        "src/stats_log.proto",
-        "src/statsd_config.proto",
         "src/atoms.proto",
         "src/shell/shell_config.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
+        "src/statsd_config.proto",
     ],
 
     static_libs: [
diff --git a/cmds/statsd/AndroidTest.xml b/cmds/statsd/AndroidTest.xml
deleted file mode 100644
index afe30a2..0000000
--- a/cmds/statsd/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for statsd_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="statsd_test->/data/nativetest/statsd_test" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/nativetest" />
-        <option name="module-name" value="statsd_test" />
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/cmds/statsd/TEST_MAPPING b/cmds/statsd/TEST_MAPPING
new file mode 100644
index 0000000..8dee073
--- /dev/null
+++ b/cmds/statsd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "statsd_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/cmds/statsd/benchmark/duration_metric_benchmark.cpp b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
index 2631009..2d315d9 100644
--- a/cmds/statsd/benchmark/duration_metric_benchmark.cpp
+++ b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
@@ -137,77 +137,74 @@
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
 
-
-    std::vector<AttributionNodeInternal> attributions1 = {
-            CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-            CreateAttribution(222, "GMSCoreModule2")};
-
-    std::vector<AttributionNodeInternal> attributions2 = {
-            CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-            CreateAttribution(555, "GMSCoreModule2")};
-
     std::vector<std::unique_ptr<LogEvent>> events;
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 11));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + 40));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 11,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 40, android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 102));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + 450));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 102,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450,
+                                                   android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 650));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + bucketSizeNs + 100));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 650,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100,
+                                                   android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + bucketSizeNs + 640));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + bucketSizeNs + 650));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 640,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 650,
+                                                   android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
+    vector<int> attributionUids1 = {9999};
+    vector<string> attributionTags1 = {""};
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 2, attributionUids1,
+                                                  attributionTags1, "job0"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1,
+                                                   attributionTags1, "job0"));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids1,
+                                                  attributionTags1, "job2"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids1,
+                                                   attributionTags1, "job2"));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850));
+    vector<int> attributionUids2 = {8888};
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2,
+                                                  attributionTags1, "job2"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850,
+                                                   attributionUids2, attributionTags1, "job2"));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900));
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 600,
+                                                  attributionUids2, attributionTags1, "job1"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900,
+                                                   attributionUids2, attributionTags1, "job1"));
 
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                          bucketStartTimeNs + 10));
-    events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                        bucketStartTimeNs + 50));
+    vector<int> attributionUids3 = {111, 222, 222};
+    vector<string> attributionTags3 = {"App1", "GMSCoreModule1", "GMSCoreModule2"};
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10, attributionUids3,
+                                          attributionTags3, "ReadEmail"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 50, attributionUids3, attributionTags3,
+                                        "ReadEmail"));
 
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                          bucketStartTimeNs + 200));
-    events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                        bucketStartTimeNs + bucketSizeNs + 300));
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200, attributionUids3,
+                                          attributionTags3, "ReadEmail"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids3,
+                                        attributionTags3, "ReadEmail"));
 
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
-                                          bucketStartTimeNs + 400));
-    events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
-                                        bucketStartTimeNs + bucketSizeNs - 1));
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids3,
+                                          attributionTags3, "ReadDoc"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids3,
+                                        attributionTags3, "ReadDoc"));
 
-    events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                          bucketStartTimeNs + 401));
-    events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                        bucketStartTimeNs + bucketSizeNs + 700));
-
+    vector<int> attributionUids4 = {333, 222, 555};
+    vector<string> attributionTags4 = {"App2", "GMSCoreModule1", "GMSCoreModule2"};
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 401, attributionUids4,
+                                          attributionTags4, "ReadEmail"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids4,
+                                        attributionTags4, "ReadEmail"));
     sortLogEventsByTimestamp(&events);
 
     while (state.KeepRunning()) {
@@ -230,78 +227,75 @@
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
 
-    std::vector<AttributionNodeInternal> attributions1 = {
-            CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-            CreateAttribution(222, "GMSCoreModule2")};
-
-    std::vector<AttributionNodeInternal> attributions2 = {
-            CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-            CreateAttribution(555, "GMSCoreModule2")};
-
-    std::vector<AttributionNodeInternal> attributions3 = {
-            CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
-            CreateAttribution(555, "GMSCoreModule2")};
-
     std::vector<std::unique_ptr<LogEvent>> events;
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 55));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + 120));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 121));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + 450));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 55,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 120,
+                                                   android::view::DISPLAY_STATE_ON));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 121,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450,
+                                                   android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 501));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + bucketSizeNs + 100));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 501,
+                                                   android::view::DISPLAY_STATE_OFF));
+    events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100,
+                                                   android::view::DISPLAY_STATE_ON));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+    vector<int> attributionUids1 = {111};
+    vector<string> attributionTags1 = {"App1"};
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 1, attributionUids1,
+                                                  attributionTags1, "job1"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1,
+                                                   attributionTags1, "job1"));
 
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
-    events.push_back(CreateStartScheduledJobEvent(
-            {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
-    events.push_back(CreateFinishScheduledJobEvent(
-            {CreateAttribution(333, "App2")}, "job2",
-            bucketStartTimeNs + bucketSizeNs + 850));
+    vector<int> attributionUids2 = {333};
+    vector<string> attributionTags2 = {"App2"};
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids2,
+                                                  attributionTags2, "job2"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids2,
+                                                   attributionTags2, "job2"));
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2,
+                                                  attributionTags2, "job2"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850,
+                                                   attributionUids2, attributionTags2, "job2"));
 
-    events.push_back(
-        CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                     bucketStartTimeNs + bucketSizeNs - 2));
-    events.push_back(
-        CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                      bucketStartTimeNs + bucketSizeNs + 900));
+    vector<int> attributionUids3 = {444};
+    vector<string> attributionTags3 = {"App3"};
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs - 2,
+                                                  attributionUids3, attributionTags3, "job3"));
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900,
+                                                   attributionUids3, attributionTags3, "job3"));
 
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                          bucketStartTimeNs + 50));
-    events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                        bucketStartTimeNs + 110));
+    vector<int> attributionUids4 = {111, 222, 222};
+    vector<string> attributionTags4 = {"App1", "GMSCoreModule1", "GMSCoreModule2"};
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids4,
+                                          attributionTags4, "ReadEmail"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 110, attributionUids4, attributionTags4,
+                                        "ReadEmail"));
 
-    events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                          bucketStartTimeNs + 300));
-    events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                        bucketStartTimeNs + bucketSizeNs + 700));
-    events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
-                                          bucketStartTimeNs + 400));
-    events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
-                                        bucketStartTimeNs + bucketSizeNs - 1));
+    vector<int> attributionUids5 = {333, 222, 555};
+    vector<string> attributionTags5 = {"App2", "GMSCoreModule1", "GMSCoreModule2"};
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 300, attributionUids5,
+                                          attributionTags5, "ReadEmail"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids5,
+                                        attributionTags5, "ReadEmail"));
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids5,
+                                          attributionTags5, "ReadDoc"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids5,
+                                        attributionTags5, "ReadDoc"));
 
-    events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                          bucketStartTimeNs + 550));
-    events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                        bucketStartTimeNs + 800));
-    events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                          bucketStartTimeNs + bucketSizeNs - 1));
-    events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                        bucketStartTimeNs + bucketSizeNs + 700));
+    vector<int> attributionUids6 = {444, 222, 555};
+    vector<string> attributionTags6 = {"App3", "GMSCoreModule1", "GMSCoreModule2"};
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 550, attributionUids6,
+                                          attributionTags6, "ReadDoc"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 800, attributionUids6, attributionTags6,
+                                        "ReadDoc"));
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids6,
+                                          attributionTags6, "ReadDoc"));
+    events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids6,
+                                        attributionTags6, "ReadDoc"));
     sortLogEventsByTimestamp(&events);
 
     while (state.KeepRunning()) {
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index cfe477d..743ccc4 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
+#include "metric_util.h"
+#include "stats_event.h"
 #include "stats_log_util.h"
 
 namespace android {
@@ -26,17 +29,20 @@
 
 using std::vector;
 
-static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matcher) {
-    AttributionNodeInternal node;
-    node.set_uid(100);
-    node.set_tag("LOCATION");
+static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matcher) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 1);
+    AStatsEvent_overwriteTimestamp(statsEvent, 100000);
 
-    std::vector<AttributionNodeInternal> nodes = {node, node};
-    event->write(nodes);
-    event->write(3.2f);
-    event->write("LOCATION");
-    event->write((int64_t)990);
-    event->init();
+    std::vector<int> attributionUids = {100, 100};
+    std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+
+    AStatsEvent_writeFloat(statsEvent, 3.2f);
+    AStatsEvent_writeString(statsEvent, "LOCATION");
+    AStatsEvent_writeInt64(statsEvent, 990);
+
+    parseStatsEventToLogEvent(statsEvent, event);
 
     field_matcher->set_field(1);
     auto child = field_matcher->add_child();
@@ -46,7 +52,7 @@
 }
 
 static void BM_FilterValue(benchmark::State& state) {
-    LogEvent event(1, 100000);
+    LogEvent event(/*uid=*/0, /*pid=*/0);
     FieldMatcher field_matcher;
     createLogEventAndMatcher(&event, &field_matcher);
 
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
index 2a4403e..7a45565 100644
--- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
+#include "metric_util.h"
+#include "stats_event.h"
 #include "stats_log_util.h"
 
 namespace android {
@@ -27,16 +30,19 @@
 using std::vector;
 
 static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) {
-    AttributionNodeInternal node;
-    node.set_uid(100);
-    node.set_tag("LOCATION");
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 1);
+    AStatsEvent_overwriteTimestamp(statsEvent, 100000);
 
-    std::vector<AttributionNodeInternal> nodes = {node, node};
-    event->write(nodes);
-    event->write(3.2f);
-    event->write("LOCATION");
-    event->write((int64_t)990);
-    event->init();
+    std::vector<int> attributionUids = {100, 100};
+    std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+
+    AStatsEvent_writeFloat(statsEvent, 3.2f);
+    AStatsEvent_writeString(statsEvent, "LOCATION");
+    AStatsEvent_writeInt64(statsEvent, 990);
+
+    parseStatsEventToLogEvent(statsEvent, event);
 
     link->conditionId = 1;
 
@@ -54,7 +60,7 @@
 
 static void BM_GetDimensionInCondition(benchmark::State& state) {
     Metric2Condition link;
-    LogEvent event(1, 100000);
+    LogEvent event(/*uid=*/0, /*pid=*/0);
     createLogEventAndLink(&event, &link);
 
     while (state.KeepRunning()) {
diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp
index 2603469..057e00b 100644
--- a/cmds/statsd/benchmark/log_event_benchmark.cpp
+++ b/cmds/statsd/benchmark/log_event_benchmark.cpp
@@ -16,55 +16,31 @@
 #include <vector>
 #include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
+#include "stats_event.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-using std::vector;
+static size_t createAndParseStatsEvent(uint8_t* msg) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 2);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_build(event);
 
-/* Special markers for android_log_list_element type */
-static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list  */
-static const char EVENT_TYPE_UNKNOWN = '?';    /* protocol error       */
-
-static const char EVENT_TYPE_INT = 0;
-static const char EVENT_TYPE_LONG = 1;
-static const char EVENT_TYPE_STRING = 2;
-static const char EVENT_TYPE_LIST = 3;
-static const char EVENT_TYPE_FLOAT = 4;
-
-static const int kLogMsgHeaderSize = 28;
-
-static void write4Bytes(int val, vector<char>* buffer) {
-    buffer->push_back(static_cast<char>(val));
-    buffer->push_back(static_cast<char>((val >> 8) & 0xFF));
-    buffer->push_back(static_cast<char>((val >> 16) & 0xFF));
-    buffer->push_back(static_cast<char>((val >> 24) & 0xFF));
-}
-
-static void getSimpleLogMsgData(log_msg* msg) {
-    vector<char> buffer;
-    // stats_log tag id
-    write4Bytes(1937006964, &buffer);
-    buffer.push_back(EVENT_TYPE_LIST);
-    buffer.push_back(2);  // field counts;
-    buffer.push_back(EVENT_TYPE_INT);
-    write4Bytes(10 /* atom id */, &buffer);
-    buffer.push_back(EVENT_TYPE_INT);
-    write4Bytes(99 /* a value to log*/, &buffer);
-    buffer.push_back(EVENT_TYPE_LIST_STOP);
-
-    msg->entry.len = buffer.size();
-    msg->entry.hdr_size = kLogMsgHeaderSize;
-    msg->entry.sec = time(nullptr);
-    std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize);
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    memcpy(msg, buf, size);
+    return size;
 }
 
 static void BM_LogEventCreation(benchmark::State& state) {
-    log_msg msg;
-    getSimpleLogMsgData(&msg);
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createAndParseStatsEvent(msg);
     while (state.KeepRunning()) {
-        benchmark::DoNotOptimize(LogEvent(msg));
+        LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001);
+        benchmark::DoNotOptimize(event.parseBuffer(msg, size));
     }
 }
 BENCHMARK(BM_LogEventCreation);
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index cca6d52..89fd3d9 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -14,6 +14,8 @@
 
 #include "metric_util.h"
 
+#include "stats_event.h"
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -245,118 +247,105 @@
     return dimensions;
 }
 
-std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-    const android::view::DisplayStateEnum state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs);
-    event->write(state);
-    event->init();
-    return event;
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
+
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
 }
 
-std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
-    int level, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs);
-    (event->write(level));
-    event->init();
-    return event;
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
+    AStatsEvent_build(statsEvent);
 
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    logEvent->parseBuffer(buf, size);
+
+    AStatsEvent_release(statsEvent);
+}
+
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+        uint64_t timestampNs, const android::view::DisplayStateEnum state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
 }
 
 std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& jobName,
-        const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(jobName);
-    event->write(state);
-    event->init();
-    return event;
+        const vector<int>& attributionUids, const vector<string>& attributionTags,
+        const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, jobName.c_str());
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
 }
 
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs) {
-    return CreateScheduledJobStateChangedEvent(
-            attributions, name, ScheduledJobStateChanged::STARTED, timestampNs);
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
+                                                       const vector<int>& attributionUids,
+                                                       const vector<string>& attributionTags,
+                                                       const string& jobName) {
+    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
+                                               ScheduledJobStateChanged::STARTED, timestampNs);
 }
 
 // Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs) {
-    return CreateScheduledJobStateChangedEvent(
-            attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs);
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
+                                                        const vector<int>& attributionUids,
+                                                        const vector<string>& attributionTags,
+                                                        const string& jobName) {
+    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
+                                               ScheduledJobStateChanged::FINISHED, timestampNs);
 }
 
-std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        const WakelockStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
-    event->write(wakelockName);
-    event->write(state);
-    event->init();
-    return event;
+std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs,
+                                                      const vector<int>& attributionUids,
+                                                      const vector<string>& attributionTags,
+                                                      const string& name,
+                                                      const SyncStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, name.c_str());
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
 }
 
-std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs) {
-    return CreateWakelockStateChangedEvent(
-        attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
+std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
+                                               const vector<int>& attributionUids,
+                                               const vector<string>& attributionTags,
+                                               const string& name) {
+    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
+                                       SyncStateChanged::ON);
 }
 
-std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs) {
-    return CreateWakelockStateChangedEvent(
-        attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
-    const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(
-        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs);
-    event->write(uid);
-    event->write("pkg_name");
-    event->write("class_name");
-    event->write(state);
-    event->init();
-    return event;
-}
-
-std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
-    return CreateActivityForegroundStateChangedEvent(
-        uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
-    return CreateActivityForegroundStateChangedEvent(
-        uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        const SyncStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(name);
-    event->write(state);
-    event->init();
-    return event;
-}
-
-std::unique_ptr<LogEvent> CreateSyncStartEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs) {
-    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateSyncEndEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs) {
-    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
+std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
+                                             const vector<int>& attributionUids,
+                                             const vector<string>& attributionTags,
+                                             const string& name) {
+    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
+                                       SyncStateChanged::OFF);
 }
 
 sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
@@ -373,13 +362,6 @@
     return processor;
 }
 
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
-    AttributionNodeInternal attribution;
-    attribution.set_uid(uid);
-    attribution.set_tag(tag);
-    return attribution;
-}
-
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
   std::sort(events->begin(), events->end(),
             [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
index 9b28d60..3efaa85 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -18,6 +18,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "src/StatsLogProcessor.h"
 #include "src/logd/LogEvent.h"
+#include "stats_event.h"
 #include "statslog.h"
 
 namespace android {
@@ -92,60 +93,38 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
 // Create log event for screen state changed.
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-    const android::view::DisplayStateEnum state, uint64_t timestampNs);
-
-// Create log event for screen brightness state changed.
-std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
-   int level, uint64_t timestampNs);
+        uint64_t timestampNs, const android::view::DisplayStateEnum state);
 
 // Create log event when scheduled job starts.
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
+                                                       const vector<int>& attributionUids,
+                                                       const vector<string>& attributionTags,
+                                                       const string& jobName);
 
 // Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs);
-
-// Create log event for app moving to background.
-std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
-
-// Create log event for app moving to foreground.
-std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
+                                                        const vector<int>& attributionUids,
+                                                        const vector<string>& attributionTags,
+                                                        const string& jobName);
 
 // Create log event when the app sync starts.
-std::unique_ptr<LogEvent> CreateSyncStartEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
+                                               const vector<int>& attributionUids,
+                                               const vector<string>& attributionTags,
+                                               const string& name);
 
 // Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateSyncEndEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs);
-
-// Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateAppCrashEvent(
-    const int uid, uint64_t timestampNs);
-
-// Create log event for acquiring wakelock.
-std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs);
-
-// Create log event for releasing wakelock.
-std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs);
-
-// Create log event for releasing wakelock.
-std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
-    int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs);
-
-// Helper function to create an AttributionNodeInternal proto.
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
+std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
+                                             const vector<int>& attributionUids,
+                                             const vector<string>& attributionTags,
+                                             const string& name);
 
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
@@ -158,4 +137,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 320a32a..c9ccfb9 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -18,7 +18,6 @@
 #include "Log.h"
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
-#include "atoms_info.h"
 #include "math.h"
 
 namespace android {
@@ -116,28 +115,13 @@
 }
 
 bool isAttributionUidField(const FieldValue& value) {
-    int field = value.mField.getField() & 0xff007f;
-    if (field == 0x10001 && value.mValue.getType() == INT) {
-        return true;
-    }
-    return false;
+    return isAttributionUidField(value.mField, value.mValue);
 }
 
 int32_t getUidIfExists(const FieldValue& value) {
-    bool isUid = false;
-    // the field is uid field if the field is the uid field in attribution node or marked as
-    // is_uid in atoms.proto
-    if (isAttributionUidField(value)) {
-        isUid = true;
-    } else {
-        auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag());
-        if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-            int uidField = it->second;  // uidField is the field number in proto
-            isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField &&
-                    value.mValue.getType() == INT;
-        }
-    }
-
+    // the field is uid field if the field is the uid field in attribution node
+    // or annotated as such in the atom
+    bool isUid = isAttributionUidField(value) || isUidField(value);
     return isUid ? value.mValue.int_value : -1;
 }
 
@@ -149,6 +133,10 @@
     return false;
 }
 
+bool isUidField(const FieldValue& fieldValue) {
+    return fieldValue.mAnnotations.isUidField();
+}
+
 Value::Value(const Value& from) {
     type = from.getType();
     switch (type) {
@@ -438,6 +426,25 @@
     return eq;
 }
 
+bool subsetDimensions(const std::vector<Matcher>& dimension_a,
+                      const std::vector<Matcher>& dimension_b) {
+    if (dimension_a.size() > dimension_b.size()) {
+        return false;
+    }
+    for (size_t i = 0; i < dimension_a.size(); ++i) {
+        bool found = false;
+        for (size_t j = 0; j < dimension_b.size(); ++j) {
+            if (dimension_a[i] == dimension_b[j]) {
+                found = true;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
 bool HasPositionANY(const FieldMatcher& matcher) {
     if (matcher.has_position() && matcher.position() == Position::ANY) {
         return true;
@@ -464,4 +471,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 6729e05..fd86e36 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -16,6 +16,7 @@
 #pragma once
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "annotations.h"
 
 namespace android {
 namespace os {
@@ -26,7 +27,6 @@
 struct Field;
 struct FieldValue;
 
-const int32_t kAttributionField = 1;
 const int32_t kMaxLogDepth = 2;
 const int32_t kLastBitMask = 0x80;
 const int32_t kClearLastBitDeco = 0x7f;
@@ -180,6 +180,7 @@
 
         return false;
     }
+
     bool matches(const Matcher& that) const;
 };
 
@@ -261,6 +262,11 @@
     return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000);
 }
 
+inline Matcher getFirstUidMatcher(int32_t atomId) {
+    int32_t pos[] = {1, 1, 1};
+    return Matcher(Field(atomId, pos, 2), 0xff7f7f7f);
+}
+
 /**
  * A wrapper for a union type to contain multiple types of values.
  *
@@ -352,6 +358,56 @@
     Value& operator=(const Value& that);
 };
 
+class Annotations {
+public:
+    Annotations() {
+        setNested(true);  // Nested = true by default
+    }
+
+    // This enum stores where particular annotations can be found in the
+    // bitmask. Note that these pos do not correspond to annotation ids.
+    enum {
+        NESTED_POS = 0x0,
+        PRIMARY_POS = 0x1,
+        EXCLUSIVE_POS = 0x2,
+        UID_POS = 0x3
+    };
+
+    inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
+
+    inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); }
+
+    inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
+
+    inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
+
+    // Default value = false
+    inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
+
+    // Default value = false
+    inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); }
+
+    // Default value = false
+    inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
+
+    // Default value = false
+    inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
+
+private:
+    inline void setBitmaskAtPos(int pos, bool value) {
+        mBooleanBitmask &= ~(1 << pos); // clear
+        mBooleanBitmask |= (value << pos); // set
+    }
+
+    inline bool getValueFromBitmask(int pos) const {
+        return (mBooleanBitmask >> pos) & 0x1;
+    }
+
+    // This is a bitmask over all annotations stored in boolean form. Because
+    // there are only 4 booleans, just one byte is required.
+    uint8_t mBooleanBitmask = 0;
+};
+
 /**
  * Represents a log item, or a dimension item (They are essentially the same).
  */
@@ -379,6 +435,7 @@
 
     Field mField;
     Value mValue;
+    Annotations mAnnotations;
 };
 
 bool HasPositionANY(const FieldMatcher& matcher);
@@ -392,9 +449,14 @@
 void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
 
 bool isAttributionUidField(const Field& field, const Value& value);
+bool isUidField(const FieldValue& fieldValue);
 
 bool equalDimensions(const std::vector<Matcher>& dimension_a,
                      const std::vector<Matcher>& dimension_b);
+
+// Returns true if dimension_a is a subset of dimension_b.
+bool subsetDimensions(const std::vector<Matcher>& dimension_a,
+                      const std::vector<Matcher>& dimension_b);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 05acef8..eba66e0 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -25,6 +25,100 @@
 
 using std::string;
 using std::vector;
+using android::base::StringPrintf;
+
+// These constants must be kept in sync with those in StatsDimensionsValue.java
+const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
+const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3;
+const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4;
+// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because
+// unused -- statsd does not correctly support bool types)
+const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6;
+const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7;
+
+/**
+ * Recursive helper function that populates a parent StatsDimensionsValueParcel
+ * with children StatsDimensionsValueParcels.
+ *
+ * \param parent parcel that will be populated with children
+ * \param childDepth depth of children FieldValues
+ * \param childPrefix expected FieldValue prefix of children
+ * \param dims vector of FieldValues stored by HashableDimensionKey
+ * \param index position in dims to start reading children from
+ */
+static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel& parent,
+                                                       int childDepth, int childPrefix,
+                                                       const vector<FieldValue>& dims,
+                                                       size_t& index) {
+    if (childDepth > 2) {
+        ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel.");
+        return;
+    }
+
+    while (index < dims.size()) {
+        const FieldValue& dim = dims[index];
+        int fieldDepth = dim.mField.getDepth();
+        int fieldPrefix = dim.mField.getPrefix(childDepth);
+
+        StatsDimensionsValueParcel child;
+        child.field = dim.mField.getPosAtDepth(childDepth);
+
+        if (fieldDepth == childDepth && fieldPrefix == childPrefix) {
+            switch (dim.mValue.getType()) {
+                case INT:
+                    child.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE;
+                    child.intValue = dim.mValue.int_value;
+                    break;
+                case LONG:
+                    child.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE;
+                    child.longValue = dim.mValue.long_value;
+                    break;
+                case FLOAT:
+                    child.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE;
+                    child.floatValue = dim.mValue.float_value;
+                    break;
+                case STRING:
+                    child.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE;
+                    child.stringValue = dim.mValue.str_value;
+                    break;
+                default:
+                    ALOGE("Encountered FieldValue with unsupported value type.");
+                    break;
+            }
+            index++;
+            parent.tupleValue.push_back(child);
+        } else if (fieldDepth > childDepth && fieldPrefix == childPrefix) {
+            // This FieldValue is not a child of the current parent, but it is
+            // an indirect descendant. Thus, create a direct child of TUPLE_TYPE
+            // and recurse to parcel the indirect descendants.
+            child.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
+            populateStatsDimensionsValueParcelChildren(child, childDepth + 1,
+                                                       dim.mField.getPrefix(childDepth + 1), dims,
+                                                       index);
+            parent.tupleValue.push_back(child);
+        } else {
+            return;
+        }
+    }
+}
+
+StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const {
+    StatsDimensionsValueParcel root;
+    if (mValues.size() == 0) {
+        return root;
+    }
+
+    root.field = mValues[0].mField.getTag();
+    root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
+
+    // Children of the root correspond to top-level (depth = 0) FieldValues.
+    int childDepth = 0;
+    int childPrefix = 0;
+    size_t index = 0;
+    populateStatsDimensionsValueParcelChildren(root, childDepth, childPrefix, mValues, index);
+
+    return root;
+}
 
 android::hash_t hashDimension(const HashableDimensionKey& value) {
     android::hash_t hash = 0;
@@ -57,6 +151,17 @@
     return JenkinsHashWhiten(hash);
 }
 
+bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values,
+                  FieldValue* output) {
+    for (const auto& value : values) {
+        if (value.mField.matches(matcherField)) {
+            (*output) = value;
+            return true;
+        }
+    }
+    return false;
+}
+
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
                   HashableDimensionKey* output) {
     size_t num_matches = 0;
@@ -75,6 +180,23 @@
     return num_matches > 0;
 }
 
+bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output) {
+    size_t num_matches = 0;
+    const int32_t simpleFieldMask = 0xff7f0000;
+    const int32_t attributionUidFieldMask = 0xff7f7f7f;
+    for (const auto& value : values) {
+        if (value.mAnnotations.isPrimaryField()) {
+            output->addValue(value);
+            output->mutableValue(num_matches)->mField.setTag(value.mField.getTag());
+            const int32_t mask =
+                    isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask;
+            output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask);
+            num_matches++;
+        }
+    }
+    return num_matches > 0;
+}
+
 void filterGaugeValues(const std::vector<Matcher>& matcherFields,
                        const std::vector<FieldValue>& values, std::vector<FieldValue>* output) {
     for (const auto& field : matcherFields) {
@@ -94,18 +216,78 @@
 
     size_t count = conditionDimension->getValues().size();
     if (count != links.conditionFields.size()) {
-        // ALOGE("WTF condition link is bad");
         return;
     }
 
     for (size_t i = 0; i < count; i++) {
         conditionDimension->mutableValue(i)->mField.setField(
-            links.conditionFields[i].mMatcher.getField());
+                links.conditionFields[i].mMatcher.getField());
         conditionDimension->mutableValue(i)->mField.setTag(
-            links.conditionFields[i].mMatcher.getTag());
+                links.conditionFields[i].mMatcher.getTag());
     }
 }
 
+void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
+                          HashableDimensionKey* statePrimaryKey) {
+    // First, get the dimension from the event using the "what" fields from the
+    // MetricStateLinks.
+    filterValues(link.metricFields, eventValues, statePrimaryKey);
+
+    // Then check that the statePrimaryKey size equals the number of state fields
+    size_t count = statePrimaryKey->getValues().size();
+    if (count != link.stateFields.size()) {
+        return;
+    }
+
+    // For each dimension Value in the statePrimaryKey, set the field and tag
+    // using the state atom fields from MetricStateLinks.
+    for (size_t i = 0; i < count; i++) {
+        statePrimaryKey->mutableValue(i)->mField.setField(link.stateFields[i].mMatcher.getField());
+        statePrimaryKey->mutableValue(i)->mField.setTag(link.stateFields[i].mMatcher.getTag());
+    }
+}
+
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+    if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+        ALOGE("Contains linked values false: whatKey is too small");
+        return false;
+    }
+
+    for (const auto& primaryValue : primaryKey.getValues()) {
+        bool found = false;
+        for (const auto& whatValue : whatKey.getValues()) {
+            if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+                primaryValue.mValue == whatValue.mValue) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField) {
+    for (auto stateLink : stateLinks) {
+        if (stateLink.stateAtomId != stateAtomId) {
+            continue;
+        }
+
+        for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+            if (stateLink.stateFields[i].mMatcher == stateField &&
+                stateLink.metricFields[i].mMatcher == metricField) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
     if (s1.size() != s2.size()) {
         return s1.size() < s2.size();
@@ -120,6 +302,10 @@
     return false;
 }
 
+bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const {
+    return !((*this) == that);
+}
+
 bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
     if (mValues.size() != that.getValues().size()) {
         return false;
@@ -173,11 +359,11 @@
 
 bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const {
     return mDimensionKeyInWhat == that.getDimensionKeyInWhat() &&
-           mDimensionKeyInCondition == that.getDimensionKeyInCondition();
+           mStateValuesKey == that.getStateValuesKey();
 };
 
 string MetricDimensionKey::toString() const {
-    return mDimensionKeyInWhat.toString() + mDimensionKeyInCondition.toString();
+    return mDimensionKeyInWhat.toString() + mStateValuesKey.toString();
 }
 
 bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const {
@@ -187,7 +373,7 @@
         return false;
     }
 
-    return mDimensionKeyInCondition < that.getDimensionKeyInCondition();
+    return mStateValuesKey < that.getStateValuesKey();
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 6f4941f..bd01100 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -16,17 +16,18 @@
 
 #pragma once
 
+#include <aidl/android/os/StatsDimensionsValueParcel.h>
 #include <utils/JenkinsHash.h>
 #include <vector>
-#include "FieldValue.h"
 #include "android-base/stringprintf.h"
+#include "FieldValue.h"
 #include "logd/LogEvent.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-using android::base::StringPrintf;
+using ::aidl::android::os::StatsDimensionsValueParcel;
 
 struct Metric2Condition {
     int64_t conditionId;
@@ -34,6 +35,12 @@
     std::vector<Matcher> conditionFields;
 };
 
+struct Metric2State {
+    int32_t stateAtomId;
+    std::vector<Matcher> metricFields;
+    std::vector<Matcher> stateFields;
+};
+
 class HashableDimensionKey {
 public:
     explicit HashableDimensionKey(const std::vector<FieldValue>& values) {
@@ -63,8 +70,12 @@
         return nullptr;
     }
 
+    StatsDimensionsValueParcel toStatsDimensionsValueParcel() const;
+
     std::string toString() const;
 
+    bool operator!=(const HashableDimensionKey& that) const;
+
     bool operator==(const HashableDimensionKey& that) const;
 
     bool operator<(const HashableDimensionKey& that) const;
@@ -76,17 +87,16 @@
 };
 
 class MetricDimensionKey {
- public:
+public:
     explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat,
-                                const HashableDimensionKey& dimensionKeyInCondition)
-        : mDimensionKeyInWhat(dimensionKeyInWhat),
-          mDimensionKeyInCondition(dimensionKeyInCondition) {};
+                                const HashableDimensionKey& stateValuesKey)
+        : mDimensionKeyInWhat(dimensionKeyInWhat), mStateValuesKey(stateValuesKey){};
 
     MetricDimensionKey(){};
 
     MetricDimensionKey(const MetricDimensionKey& that)
         : mDimensionKeyInWhat(that.getDimensionKeyInWhat()),
-          mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {};
+          mStateValuesKey(that.getStateValuesKey()){};
 
     MetricDimensionKey& operator=(const MetricDimensionKey& from) = default;
 
@@ -96,30 +106,41 @@
         return mDimensionKeyInWhat;
     }
 
-    inline const HashableDimensionKey& getDimensionKeyInCondition() const {
-        return mDimensionKeyInCondition;
+    inline const HashableDimensionKey& getStateValuesKey() const {
+        return mStateValuesKey;
     }
 
-    inline void setDimensionKeyInCondition(const HashableDimensionKey& key) {
-        mDimensionKeyInCondition = key;
+    inline HashableDimensionKey* getMutableStateValuesKey() {
+        return &mStateValuesKey;
     }
 
-    bool hasDimensionKeyInCondition() const {
-        return mDimensionKeyInCondition.getValues().size() > 0;
+    inline void setStateValuesKey(const HashableDimensionKey& key) {
+        mStateValuesKey = key;
+    }
+
+    bool hasStateValuesKey() const {
+        return mStateValuesKey.getValues().size() > 0;
     }
 
     bool operator==(const MetricDimensionKey& that) const;
 
     bool operator<(const MetricDimensionKey& that) const;
 
-  private:
-      HashableDimensionKey mDimensionKeyInWhat;
-      HashableDimensionKey mDimensionKeyInCondition;
+private:
+    HashableDimensionKey mDimensionKeyInWhat;
+    HashableDimensionKey mStateValuesKey;
 };
 
 android::hash_t hashDimension(const HashableDimensionKey& key);
 
 /**
+ * Returns true if a FieldValue field matches the matcher field.
+ * The value of the FieldValue is output.
+ */
+bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values,
+                  FieldValue* output);
+
+/**
  * Creating HashableDimensionKeys from FieldValues using matcher.
  *
  * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
@@ -133,6 +154,18 @@
                   HashableDimensionKey* output);
 
 /**
+ * Creating HashableDimensionKeys from State Primary Keys in FieldValues.
+ *
+ * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
+ * in it. This is because: for example, when we create dimension from last uid in attribution chain,
+ * In one event, uid 1000 is at position 5 and it's the last
+ * In another event, uid 1000 is at position 6, and it's the last
+ * these 2 events should be mapped to the same dimension.  So we will remove the original position
+ * from the dimension key for the uid field (by applying 0x80 bit mask).
+ */
+bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output);
+
+/**
  * Filter the values from FieldValues using the matchers.
  *
  * In contrast to the above function, this function will not do any modification to the original
@@ -145,6 +178,39 @@
                               const Metric2Condition& links,
                               HashableDimensionKey* conditionDimension);
 
+/**
+ * Get dimension values using metric's "what" fields and fill statePrimaryKey's
+ * mField information using "state" fields.
+ */
+void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
+                          HashableDimensionKey* statePrimaryKey);
+
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const std::vector<Metric2State>& stateLinks,
+                               const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
@@ -165,8 +231,8 @@
 struct hash<MetricDimensionKey> {
     std::size_t operator()(const MetricDimensionKey& key) const {
         android::hash_t hash = hashDimension(key.getDimensionKeyInWhat());
-        hash = android::JenkinsHashMix(hash, hashDimension(key.getDimensionKeyInCondition()));
+        hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey()));
         return android::JenkinsHashWhiten(hash);
     }
 };
-}  // namespace std
\ No newline at end of file
+}  // namespace std
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index a730a0d..e7b32c5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -20,16 +20,20 @@
 #include "StatsLogProcessor.h"
 
 #include <android-base/file.h>
+#include <cutils/multiuser.h>
 #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h>
 
 #include "android-base/stringprintf.h"
-#include "atoms_info.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
 #include "metrics/CountMetricProducer.h"
+#include "StatsService.h"
+#include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
-#include "statslog.h"
+#include "statslog_statsd.h"
 #include "storage/StorageManager.h"
 
 using namespace android;
@@ -67,9 +71,14 @@
 // for ActiveConfigList
 const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
 
+// for permissions checks
+constexpr const char* kPermissionDump = "android.permission.DUMP";
+constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
+
 #define NS_PER_HOUR 3600 * NS_PER_SEC
 
 #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric"
+#define STATS_METADATA_DIR "/data/misc/stats-metadata"
 
 // Cool down period for writing data to disk to avoid overwriting files.
 #define WRITE_DATA_COOL_DOWN_SEC 5
@@ -92,6 +101,7 @@
       mLargestTimestampSeen(0),
       mLastTimestampSeen(0) {
     mPullerManager->ForceClearPullerCache();
+    StateManager::getInstance().updateLogSources(uidMap);
 }
 
 StatsLogProcessor::~StatsLogProcessor() {
@@ -128,38 +138,22 @@
     }
 }
 
-void updateUid(Value* value, int hostUid) {
-    int uid = value->int_value;
-    if (uid != hostUid) {
-        value->setInt(hostUid);
-    }
-}
-
 void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
-    if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) !=
-        android::util::AtomsInfo::kAtomsWithAttributionChain.end()) {
-        for (auto& value : *(event->getMutableValues())) {
-            if (value.mField.getPosAtDepth(0) > kAttributionField) {
-                break;
-            }
-            if (isAttributionUidField(value)) {
-                const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value);
-                updateUid(&value.mValue, hostUid);
+    if (std::pair<int, int> indexRange; event->hasAttributionChain(&indexRange)) {
+        vector<FieldValue>* const fieldValues = event->getMutableValues();
+        for (int i = indexRange.first; i <= indexRange.second; i++) {
+            FieldValue& fieldValue = fieldValues->at(i);
+            if (isAttributionUidField(fieldValue)) {
+                const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value);
+                fieldValue.mValue.setInt(hostUid);
             }
         }
     } else {
-        auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId());
-        if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-            int uidField = it->second;  // uidField is the field number in proto,
-                                        // starting from 1
-            if (uidField > 0 && (int)event->getValues().size() >= uidField &&
-                (event->getValues())[uidField - 1].mValue.getType() == INT) {
-                Value& value = (*event->getMutableValues())[uidField - 1].mValue;
-                const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
-                updateUid(&value, hostUid);
-            } else {
-                ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
-            }
+        int uidFieldIndex = event->getUidFieldIndex();
+        if (uidFieldIndex != -1) {
+           Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+           const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
+           value.setInt(hostUid);
         }
     }
 }
@@ -180,6 +174,200 @@
     }
 }
 
+void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) {
+    pid_t pid = event->GetPid();
+    uid_t uid = event->GetUid();
+    if (!checkPermissionForIds(kPermissionDump, pid, uid) ||
+        !checkPermissionForIds(kPermissionUsage, pid, uid)) {
+        return;
+    }
+    // The Get* functions don't modify the status on success, they only write in
+    // failure statuses, so we can use one status variable for all calls then
+    // check if it is no longer NO_ERROR.
+    status_t err = NO_ERROR;
+    InstallTrainInfo trainInfo;
+    trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err));
+    trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err);
+    trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err);
+    trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err);
+    trainInfo.requiresLowLatencyMonitor =
+            event->GetBool(5 /*requires low latency monitor field id*/, &err);
+    trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err));
+    std::vector<uint8_t> trainExperimentIdBytes =
+            event->GetStorage(7 /*experiment ids field id*/, &err);
+    bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err);
+
+    if (err != NO_ERROR) {
+        ALOGE("Failed to parse fields in binary push state changed log event");
+        return;
+    }
+    ExperimentIds trainExperimentIds;
+    if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(),
+                                           trainExperimentIdBytes.size())) {
+        ALOGE("Failed to parse experimentids in binary push state changed.");
+        return;
+    }
+    trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(),
+                               trainExperimentIds.experiment_id().end()};
+
+    // Update the train info on disk and get any data the logevent is missing.
+    getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo);
+
+    std::vector<uint8_t> trainExperimentIdProto;
+    writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto);
+    int32_t userId = multiuser_get_user_id(uid);
+
+    event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG);
+    event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE);
+    event->updateValue(8 /*user id field id*/, userId, INT);
+
+    // If this event is a rollback event, then the following bits in the event
+    // are invalid and we will need to update them with the values we pulled
+    // from disk.
+    if (is_rollback) {
+        int bit = trainInfo.requiresStaging ? 1 : 0;
+        event->updateValue(3 /*requires staging field id*/, bit, INT);
+        bit = trainInfo.rollbackEnabled ? 1 : 0;
+        event->updateValue(4 /*rollback enabled field id*/, bit, INT);
+        bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0;
+        event->updateValue(5 /*requires low latency monitor field id*/, bit, INT);
+    }
+}
+
+void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback,
+                                                    InstallTrainInfo* trainInfo) {
+    // If the train name is empty, we don't know which train to attribute the
+    // event to, so return early.
+    if (trainInfo->trainName.empty()) {
+        return;
+    }
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk);
+
+    bool resetExperimentIds = false;
+    if (readTrainInfoSuccess) {
+        // Keep the old train version if we received an empty version.
+        if (trainInfo->trainVersionCode == -1) {
+            trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode;
+        } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) {
+            // Reset experiment ids if we receive a new non-empty train version.
+            resetExperimentIds = true;
+        }
+
+        // Reset if we received a different experiment id.
+        if (!trainInfo->experimentIds.empty() &&
+            (trainInfoOnDisk.experimentIds.empty() ||
+             trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) {
+            resetExperimentIds = true;
+        }
+    }
+
+    // Find the right experiment IDs
+    if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) {
+        trainInfo->experimentIds = trainInfoOnDisk.experimentIds;
+    }
+
+    if (!trainInfo->experimentIds.empty()) {
+        int64_t firstId = trainInfo->experimentIds.at(0);
+        auto& ids = trainInfo->experimentIds;
+        switch (trainInfo->status) {
+            case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
+                if (find(ids.begin(), ids.end(), firstId + 1) == ids.end()) {
+                    ids.push_back(firstId + 1);
+                }
+                break;
+            case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
+                if (find(ids.begin(), ids.end(), firstId + 2) == ids.end()) {
+                    ids.push_back(firstId + 2);
+                }
+                break;
+            case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
+                if (find(ids.begin(), ids.end(), firstId + 3) == ids.end()) {
+                    ids.push_back(firstId + 3);
+                }
+                break;
+        }
+    }
+
+    // If this event is a rollback event, the following fields are invalid and
+    // need to be replaced by the fields stored to disk.
+    if (is_rollback) {
+        trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging;
+        trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled;
+        trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor;
+    }
+
+    StorageManager::writeTrainInfo(*trainInfo);
+}
+
+void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) {
+    pid_t pid = event->GetPid();
+    uid_t uid = event->GetUid();
+    if (!checkPermissionForIds(kPermissionDump, pid, uid) ||
+        !checkPermissionForIds(kPermissionUsage, pid, uid)) {
+        return;
+    }
+    // The Get* functions don't modify the status on success, they only write in
+    // failure statuses, so we can use one status variable for all calls then
+    // check if it is no longer NO_ERROR.
+    status_t err = NO_ERROR;
+    int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err));
+    string packageName = string(event->GetString(2 /*package name field id*/, &err));
+
+    if (err != NO_ERROR) {
+        ALOGE("Failed to parse fields in watchdog rollback occurred log event");
+        return;
+    }
+
+    vector<int64_t> experimentIds =
+        processWatchdogRollbackOccurred(rollbackType, packageName);
+    vector<uint8_t> experimentIdProto;
+    writeExperimentIdsToProto(experimentIds, &experimentIdProto);
+
+    event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE);
+}
+
+vector<int64_t> StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
+                                                                    const string& packageNameIn) {
+    // If the package name is empty, we can't attribute it to any train, so
+    // return early.
+    if (packageNameIn.empty()) {
+      return vector<int64_t>();
+    }
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    // We use the package name of the event as the train name.
+    readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk);
+
+    if (!readTrainInfoSuccess) {
+        return vector<int64_t>();
+    }
+
+    if (trainInfoOnDisk.experimentIds.empty()) {
+        return vector<int64_t>();
+    }
+
+    int64_t firstId = trainInfoOnDisk.experimentIds[0];
+    auto& ids = trainInfoOnDisk.experimentIds;
+    switch (rollbackTypeIn) {
+      case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
+            if (find(ids.begin(), ids.end(), firstId + 4) == ids.end()) {
+                ids.push_back(firstId + 4);
+            }
+            StorageManager::writeTrainInfo(trainInfoOnDisk);
+            break;
+      case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
+            if (find(ids.begin(), ids.end(), firstId + 5) == ids.end()) {
+                ids.push_back(firstId + 5);
+            }
+            StorageManager::writeTrainInfo(trainInfoOnDisk);
+            break;
+    }
+
+    return trainInfoOnDisk.experimentIds;
+}
+
 void StatsLogProcessor::resetConfigs() {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     resetConfigsLocked(getElapsedRealtimeNs());
@@ -194,26 +382,51 @@
 }
 
 void StatsLogProcessor::OnLogEvent(LogEvent* event) {
+    OnLogEvent(event, getElapsedRealtimeNs());
+}
+
+void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    // Tell StatsdStats about new event
+    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
+    int atomId = event->GetTagId();
+    StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+    if (!event->isValid()) {
+        StatsdStats::getInstance().noteAtomError(atomId);
+        return;
+    }
+
+    // Hard-coded logic to update train info on disk and fill in any information
+    // this log event may be missing.
+    if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
+        onBinaryPushStateChangedEventLocked(event);
+    }
+
+    // Hard-coded logic to update experiment ids on disk for certain rollback
+    // types and fill the rollback atom with experiment ids
+    if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
+        onWatchdogRollbackOccurredLocked(event);
+    }
+
 #ifdef VERY_VERBOSE_PRINTING
     if (mPrintAllLogs) {
         ALOGI("%s", event->ToString().c_str());
     }
 #endif
-    const int64_t currentTimestampNs = event->GetElapsedTimestampNs();
-
-    resetIfConfigTtlExpiredLocked(currentTimestampNs);
-
-    StatsdStats::getInstance().noteAtomLogged(
-        event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
+    resetIfConfigTtlExpiredLocked(eventElapsedTimeNs);
 
     // Hard-coded logic to update the isolated uid's in the uid-map.
     // The field numbers need to be currently updated by hand with atoms.proto
-    if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) {
+    if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) {
         onIsolatedUidChangedEventLocked(*event);
+    } else {
+        // Map the isolated uid to host uid if necessary.
+        mapIsolatedUidToHostUidIfNecessaryLocked(event);
     }
 
+    StateManager::getInstance().onLogEvent(*event);
+
     if (mMetricsManagers.empty()) {
         return;
     }
@@ -224,12 +437,6 @@
         mLastPullerCacheClearTimeSec = curTimeSec;
     }
 
-
-    if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) {
-        // Map the isolated uid to host uid if necessary.
-        mapIsolatedUidToHostUidIfNecessaryLocked(event);
-    }
-
     std::unordered_set<int> uidsWithActiveConfigsChanged;
     std::unordered_map<int, std::vector<int64_t>> activeConfigsPerUid;
     // pass the event to metrics managers.
@@ -256,15 +463,16 @@
             uidsWithActiveConfigsChanged.insert(uid);
             StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive);
         }
-        flushIfNecessaryLocked(event->GetElapsedTimestampNs(), pair.first, *(pair.second));
+        flushIfNecessaryLocked(pair.first, *(pair.second));
     }
 
+    // Don't use the event timestamp for the guardrail.
     for (int uid : uidsWithActiveConfigsChanged) {
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid);
         if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) {
-            if (currentTimestampNs - lastBroadcastTime->second <
-                    StatsdStats::kMinActivationBroadcastPeriodNs) {
+            if (elapsedRealtimeNs - lastBroadcastTime->second <
+                StatsdStats::kMinActivationBroadcastPeriodNs) {
                 StatsdStats::getInstance().noteActivationBroadcastGuardrailHit(uid);
                 VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us.");
                 return;
@@ -274,13 +482,13 @@
         if (activeConfigs != activeConfigsPerUid.end()) {
             if (mSendActivationBroadcast(uid, activeConfigs->second)) {
                 VLOG("StatsD sent activation notice for uid %d", uid);
-                mLastActivationBroadcastTimes[uid] = currentTimestampNs;
+                mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs;
             }
         } else {
             std::vector<int64_t> emptyActiveConfigs;
             if (mSendActivationBroadcast(uid, emptyActiveConfigs)) {
                 VLOG("StatsD sent EMPTY activation notice for uid %d", uid);
-                mLastActivationBroadcastTimes[uid] = currentTimestampNs;
+                mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs;
             }
         }
     }
@@ -314,13 +522,16 @@
             new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager,
                                mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
     if (newMetricsManager->isConfigValid()) {
+        newMetricsManager->init();
         mUidMap->OnConfigUpdated(key);
         newMetricsManager->refreshTtl(timestampNs);
         mMetricsManagers[key] = newMetricsManager;
         VLOG("StatsdConfig valid");
     } else {
         // If there is any error in the config, don't use it.
+        // Remove any existing config with the same key.
         ALOGE("StatsdConfig NOT valid");
+        mMetricsManagers.erase(key);
     }
 }
 
@@ -537,22 +748,23 @@
     }
 }
 
-void StatsLogProcessor::flushIfNecessaryLocked(
-    int64_t timestampNs, const ConfigKey& key, MetricsManager& metricsManager) {
+void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key,
+                                               MetricsManager& metricsManager) {
+    int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
     auto lastCheckTime = mLastByteSizeTimes.find(key);
     if (lastCheckTime != mLastByteSizeTimes.end()) {
-        if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
+        if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
             return;
         }
     }
 
     // We suspect that the byteSize() computation is expensive, so we set a rate limit.
     size_t totalBytes = metricsManager.byteSize();
-    mLastByteSizeTimes[key] = timestampNs;
+    mLastByteSizeTimes[key] = elapsedRealtimeNs;
     bool requestDump = false;
-    if (totalBytes >
-        StatsdStats::kMaxMetricsBytesPerConfig) {  // Too late. We need to start clearing data.
-        metricsManager.dropData(timestampNs);
+    if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) {
+        // Too late. We need to start clearing data.
+        metricsManager.dropData(elapsedRealtimeNs);
         StatsdStats::getInstance().noteDataDropped(key, totalBytes);
         VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
     } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
@@ -567,7 +779,8 @@
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastBroadcastTimes.find(key);
         if (lastBroadcastTime != mLastBroadcastTimes.end()) {
-            if (timestampNs - lastBroadcastTime->second < StatsdStats::kMinBroadcastPeriodNs) {
+            if (elapsedRealtimeNs - lastBroadcastTime->second <
+                    StatsdStats::kMinBroadcastPeriodNs) {
                 VLOG("StatsD would've sent a broadcast but the rate limit stopped us.");
                 return;
             }
@@ -575,7 +788,7 @@
         if (mSendBroadcast(key)) {
             mOnDiskDataConfigs.erase(key);
             VLOG("StatsD triggered data fetch for %s", key.ToString().c_str());
-            mLastBroadcastTimes[key] = timestampNs;
+            mLastBroadcastTimes[key] = elapsedRealtimeNs;
             StatsdStats::getInstance().noteBroadcastSent(key);
         }
     }
@@ -627,6 +840,110 @@
     proto.flush(fd.get());
 }
 
+void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs,
+                                           int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    // Do not write to disk if we already have in the last few seconds.
+    if (static_cast<unsigned long long> (systemElapsedTimeNs) <
+            mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) {
+        ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds",
+                WRITE_DATA_COOL_DOWN_SEC);
+        return;
+    }
+    mLastMetadataWriteNs = systemElapsedTimeNs;
+
+    metadata::StatsMetadataList metadataList;
+    WriteMetadataToProtoLocked(
+            currentWallClockTimeNs, systemElapsedTimeNs, &metadataList);
+
+    string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+    StorageManager::deleteFile(file_name.c_str());
+
+    if (metadataList.stats_metadata_size() == 0) {
+        // Skip the write if we have nothing to write.
+        return;
+    }
+
+    std::string data;
+    metadataList.SerializeToString(&data);
+    StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size());
+}
+
+void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs,
+                                             int64_t systemElapsedTimeNs,
+                                             metadata::StatsMetadataList* metadataList) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList);
+}
+
+void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+                                                   int64_t systemElapsedTimeNs,
+                                                   metadata::StatsMetadataList* metadataList) {
+    for (const auto& pair : mMetricsManagers) {
+        const sp<MetricsManager>& metricsManager = pair.second;
+        metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata();
+        bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs,
+                systemElapsedTimeNs, statsMetadata);
+        if (!metadataWritten) {
+            metadataList->mutable_stats_metadata()->RemoveLast();
+        }
+    }
+}
+
+void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+                                             int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+    int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
+    if (-1 == fd) {
+        VLOG("Attempt to read %s but failed", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+    string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        ALOGE("Attempt to read %s but failed", file_name.c_str());
+        close(fd);
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+
+    close(fd);
+
+    metadata::StatsMetadataList statsMetadataList;
+    if (!statsMetadataList.ParseFromString(content)) {
+        ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+    SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+    StorageManager::deleteFile(file_name.c_str());
+}
+
+void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+                                         int64_t currentWallClockTimeNs,
+                                         int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+}
+
+void StatsLogProcessor::SetMetadataStateLocked(
+        const metadata::StatsMetadataList& statsMetadataList,
+        int64_t currentWallClockTimeNs,
+        int64_t systemElapsedTimeNs) {
+    for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) {
+        ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id());
+        auto it = mMetricsManagers.find(key);
+        if (it == mMetricsManagers.end()) {
+            ALOGE("No config found for configKey %s", key.ToString().c_str());
+            continue;
+        }
+        VLOG("Setting metadata %s", key.ToString().c_str());
+        it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs);
+    }
+    VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size());
+}
+
 void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream(
         int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
@@ -736,8 +1053,9 @@
 void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
                                          const int uid, const int64_t version) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received app upgrade");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received app upgrade");
+    StateManager::getInstance().notifyAppChanged(apk, mUidMap);
+    for (const auto& it : mMetricsManagers) {
         it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version);
     }
 }
@@ -745,20 +1063,30 @@
 void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
                                          const int uid) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received app removed");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received app removed");
+    StateManager::getInstance().notifyAppChanged(apk, mUidMap);
+    for (const auto& it : mMetricsManagers) {
         it.second->notifyAppRemoved(eventTimeNs, apk, uid);
     }
 }
 
 void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received uid map");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received uid map");
+    StateManager::getInstance().updateLogSources(mUidMap);
+    for (const auto& it : mMetricsManagers) {
         it.second->onUidMapReceived(eventTimeNs);
     }
 }
 
+void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    VLOG("Received boot completed signal");
+    for (const auto& it : mMetricsManagers) {
+        it.second->onStatsdInitCompleted(elapsedTimeNs);
+    }
+}
+
 void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     mOnDiskDataConfigs.insert(key);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 0d2b33e..23f2584 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -18,11 +18,13 @@
 
 #include <gtest/gtest_prod.h>
 #include "config/ConfigListener.h"
+#include "logd/LogEvent.h"
 #include "metrics/MetricsManager.h"
 #include "packages/UidMap.h"
 #include "external/StatsPullerManager.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 
 #include <stdio.h>
 #include <unordered_map>
@@ -88,6 +90,23 @@
     /* Load configs containing metrics with active activations from disk. */
     void LoadActiveConfigsFromDisk();
 
+    /* Persist metadata for configs and metrics to disk. */
+    void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs);
+
+    /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */
+    void WriteMetadataToProto(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs,
+                              metadata::StatsMetadataList* metadataList);
+
+    /* Load stats metadata for configs and metrics from disk. */
+    void LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs);
+
+    /* Sets the metadata for all configs and metrics */
+    void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+                          int64_t currentWallClockTimeNs,
+                          int64_t systemElapsedTimeNs);
+
     /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
     void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
 
@@ -101,6 +120,11 @@
     /* Notify all MetricsManagers of uid map snapshots received */
     void onUidMapReceived(const int64_t& eventTimeNs) override;
 
+    /* Notify all metrics managers of boot completed
+     * This will force a bucket split when the boot is finished.
+     */
+    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
     // Reset all configs.
     void resetConfigs();
 
@@ -157,6 +181,8 @@
 
     sp<AlarmMonitor> mPeriodicAlarmMonitor;
 
+    void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs);
+
     void resetIfConfigTtlExpiredLocked(const int64_t timestampNs);
 
     void OnConfigUpdatedLocked(
@@ -170,8 +196,17 @@
     void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList,
                                      int64_t currentTimeNs);
 
+    void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList,
+                                int64_t currentWallClockTimeNs,
+                                int64_t systemElapsedTimeNs);
+
+    void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+                                    int64_t systemElapsedTimeNs,
+                                    metadata::StatsMetadataList* metadataList);
+
     void WriteDataToDiskLocked(const DumpReportReason dumpReportReason,
                                const DumpLatency dumpLatency);
+
     void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs,
                                const DumpReportReason dumpReportReason,
                                const DumpLatency dumpLatency);
@@ -186,8 +221,7 @@
 
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
-    void flushIfNecessaryLocked(int64_t timestampNs, const ConfigKey& key,
-                                MetricsManager& metricsManager);
+    void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager);
 
     // Maps the isolated uid in the log event to host uid if the log event contains uid fields.
     void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const;
@@ -195,6 +229,22 @@
     // Handler over the isolated uid change event.
     void onIsolatedUidChangedEventLocked(const LogEvent& event);
 
+    // Handler over the binary push state changed event.
+    void onBinaryPushStateChangedEventLocked(LogEvent* event);
+
+    // Handler over the watchdog rollback occurred event.
+    void onWatchdogRollbackOccurredLocked(LogEvent* event);
+
+    // Updates train info on disk based on binary push state changed info and
+    // write disk info into parameters.
+    void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn);
+
+    // Gets experiment ids on disk for associated train and updates them
+    // depending on rollback type. Then writes them back to disk and returns
+    // them.
+    std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
+                                                          const string& packageName);
+
     // Reset all configs.
     void resetConfigsLocked(const int64_t timestampNs);
     // Reset the specified configs.
@@ -223,6 +273,9 @@
     // Last time we wrote active metrics to disk.
     int64_t mLastActiveMetricsWriteNs = 0;
 
+    //Last time we wrote metadata to disk.
+    int64_t mLastMetadataWriteNs = 0;
+
 #ifdef VERY_VERBOSE_PRINTING
     bool mPrintAllLogs = false;
 #endif
@@ -231,6 +284,7 @@
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
@@ -254,25 +308,12 @@
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
     FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
-
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition);
-
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition);
-
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
 
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -285,12 +326,31 @@
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
+    FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
+    FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+
+    FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index d21c10c..3226482 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -28,14 +28,11 @@
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/PermissionController.h>
 #include <cutils/multiuser.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <frameworks/base/cmds/statsd/src/uid_data.pb.h>
 #include <private/android_filesystem_config.h>
-#include <statslog.h>
+#include <statslog_statsd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/system_properties.h>
@@ -48,79 +45,44 @@
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_MESSAGE;
 
+using Status = ::ndk::ScopedAStatus;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 constexpr const char* kPermissionDump = "android.permission.DUMP";
-constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
 
-constexpr const char* kOpUsage = "android:get_usage_stats";
+constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM";
 
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 // for StatsDataDumpProto
 const int FIELD_ID_REPORTS_LIST = 1;
 
-static binder::Status ok() {
-    return binder::Status::ok();
-}
-
-static binder::Status exception(uint32_t code, const std::string& msg) {
+static Status exception(int32_t code, const std::string& msg) {
     ALOGE("%s (%d)", msg.c_str(), code);
-    return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+    return Status::fromExceptionCodeWithMessage(code, msg.c_str());
 }
 
-binder::Status checkUid(uid_t expectedUid) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
+static bool checkPermission(const char* permission) {
+    pid_t pid = AIBinder_getCallingPid();
+    uid_t uid = AIBinder_getCallingUid();
+    return checkPermissionForIds(permission, pid, uid);
+}
+
+Status checkUid(uid_t expectedUid) {
+    uid_t uid = AIBinder_getCallingUid();
     if (uid == expectedUid || uid == AID_ROOT) {
-        return ok();
+        return Status::ok();
     } else {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
-    }
-}
-
-binder::Status checkDumpAndUsageStats(const String16& packageName) {
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    // Root, system, and shell always have access
-    if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) {
-        return ok();
-    }
-
-    // Caller must be granted these permissions
-    if (!checkCallingPermission(String16(kPermissionDump))) {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump));
-    }
-    if (!checkCallingPermission(String16(kPermissionUsage))) {
-        return exception(binder::Status::EX_SECURITY,
-                StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage));
-    }
-
-    // Caller must also have usage stats op granted
-    PermissionController pc;
-    switch (pc.noteOp(String16(kOpUsage), uid, packageName)) {
-        case PermissionController::MODE_ALLOWED:
-        case PermissionController::MODE_DEFAULT:
-            return ok();
-        default:
-            return exception(binder::Status::EX_SECURITY,
-                    StringPrintf("UID %d / PID %d lacks app-op %s", uid, pid, kOpUsage));
+        return exception(EX_SECURITY,
+                         StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
     }
 }
 
 #define ENFORCE_UID(uid) {                                        \
-    binder::Status status = checkUid((uid));                      \
-    if (!status.isOk()) {                                         \
-        return status;                                            \
-    }                                                             \
-}
-
-#define ENFORCE_DUMP_AND_USAGE_STATS(packageName) {               \
-    binder::Status status = checkDumpAndUsageStats(packageName);  \
+    Status status = checkUid((uid));                              \
     if (!status.isOk()) {                                         \
         return status;                                            \
     }                                                             \
@@ -129,13 +91,13 @@
 StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQueue> queue)
     : mAnomalyAlarmMonitor(new AlarmMonitor(
               MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
-              [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+              [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
                   if (sc != nullptr) {
                       sc->setAnomalyAlarm(timeMillis);
                       StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
                   }
               },
-              [](const sp<IStatsCompanionService>& sc) {
+              [](const shared_ptr<IStatsCompanionService>& sc) {
                   if (sc != nullptr) {
                       sc->cancelAnomalyAlarm();
                       StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
@@ -143,19 +105,23 @@
               })),
       mPeriodicAlarmMonitor(new AlarmMonitor(
               MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
-              [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+              [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
                   if (sc != nullptr) {
                       sc->setAlarmForSubscriberTriggering(timeMillis);
                       StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
                   }
               },
-              [](const sp<IStatsCompanionService>& sc) {
+              [](const shared_ptr<IStatsCompanionService>& sc) {
                   if (sc != nullptr) {
                       sc->cancelAlarmForSubscriberTriggering();
                       StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
                   }
               })),
-      mEventQueue(queue) {
+      mEventQueue(queue),
+      mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag},
+                           [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }),
+      mStatsCompanionServiceDeathRecipient(
+              AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) {
     mUidMap = UidMap::getInstance();
     mPullerManager = new StatsPullerManager();
     StatsPuller::SetUidMap(mUidMap);
@@ -164,33 +130,30 @@
             mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
             getElapsedRealtimeNs(),
             [this](const ConfigKey& key) {
-                sp<IStatsCompanionService> sc = getStatsCompanionService();
-                auto receiver = mConfigManager->GetConfigReceiver(key);
-                if (sc == nullptr) {
-                    VLOG("Could not find StatsCompanionService");
+                shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+                if (receiver == nullptr) {
+                    VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str());
                     return false;
-                } else if (receiver == nullptr) {
-                    VLOG("Statscompanion could not find a broadcast receiver for %s",
-                         key.ToString().c_str());
-                    return false;
-                } else {
-                    sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+                } else if (receiver->sendDataBroadcast(
+                           mProcessor->getLastReportTimeNs(key)).isOk()) {
                     return true;
+                } else {
+                    VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str());
+                    return false;
                 }
             },
             [this](const int& uid, const vector<int64_t>& activeConfigs) {
-                auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
-                sp<IStatsCompanionService> sc = getStatsCompanionService();
-                if (sc == nullptr) {
-                    VLOG("Could not access statsCompanion");
-                    return false;
-                } else if (receiver == nullptr) {
+                shared_ptr<IPendingIntentRef> receiver =
+                    mConfigManager->GetActiveConfigsChangedReceiver(uid);
+                if (receiver == nullptr) {
                     VLOG("Could not find receiver for uid %d", uid);
                     return false;
-                } else {
-                    sc->sendActiveConfigsChangedBroadcast(receiver, activeConfigs);
+                } else if (receiver->sendActiveConfigsChangedBroadcast(activeConfigs).isOk()) {
                     VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid);
                     return true;
+                } else {
+                    VLOG("StatsService::active configs broadcast failed for uid %d" , uid);
+                    return false;
                 }
             });
 
@@ -241,36 +204,6 @@
 }
 
 /**
- * Implement our own because the default binder implementation isn't
- * properly handling SHELL_COMMAND_TRANSACTION.
- */
-status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                                  uint32_t flags) {
-    switch (code) {
-        case SHELL_COMMAND_TRANSACTION: {
-            int in = data.readFileDescriptor();
-            int out = data.readFileDescriptor();
-            int err = data.readFileDescriptor();
-            int argc = data.readInt32();
-            Vector<String8> args;
-            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
-                args.add(String8(data.readString16()));
-            }
-            sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
-            sp<IResultReceiver> resultReceiver =
-                    IResultReceiver::asInterface(data.readStrongBinder());
-
-            err = command(in, out, err, args, resultReceiver);
-            if (resultReceiver != nullptr) {
-                resultReceiver->send(err);
-            }
-            return NO_ERROR;
-        }
-        default: { return BnStatsManager::onTransact(code, data, reply, flags); }
-    }
-}
-
-/**
  * Write data from statsd.
  * Format for statsdStats:  adb shell dumpsys stats --metadata [-v] [--proto]
  * Format for data report:  adb shell dumpsys stats [anything other than --metadata] [--proto]
@@ -279,20 +212,21 @@
  *     (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto")
  * TODO: Come up with a more robust method of enacting <serviceutils/PriorityDumper.h>.
  */
-status_t StatsService::dump(int fd, const Vector<String16>& args) {
-    if (!checkCallingPermission(String16(kPermissionDump))) {
+status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) {
+    if (!checkPermission(kPermissionDump)) {
         return PERMISSION_DENIED;
     }
-    int lastArg = args.size() - 1;
+
+    int lastArg = numArgs - 1;
     bool asProto = false;
-    if (lastArg >= 0 && !args[lastArg].compare(String16("--proto"))) { // last argument
+    if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument
         asProto = true;
         lastArg--;
     }
-    if (args.size() > 0 && !args[0].compare(String16("--metadata"))) { // first argument
+    if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument
         // Request is to dump statsd stats.
         bool verbose = false;
-        if (lastArg >= 0 && !args[lastArg].compare(String16("-v"))) {
+        if (lastArg >= 0 && string(args[lastArg]) == "-v") {
             verbose = true;
             lastArg--;
         }
@@ -333,8 +267,11 @@
     for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) {
         uint64_t reportsListToken =
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST);
+        // Don't include the current bucket to avoid skipping buckets.
+        // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS
+        // or other alternatives to avoid skipping buckets for pulled metrics.
         mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(),
-                                 true /* includeCurrentBucket */, false /* erase_data */,
+                                 false /* includeCurrentBucket */, false /* erase_data */,
                                  ADB_DUMP,
                                  FAST,
                                  &proto);
@@ -347,67 +284,74 @@
 /**
  * Implementation of the adb shell cmd stats command.
  */
-status_t StatsService::command(int in, int out, int err, Vector<String8>& args,
-                               sp<IResultReceiver> resultReceiver) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
+status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv,
+                                          uint32_t argc) {
+    uid_t uid = AIBinder_getCallingUid();
     if (uid != AID_ROOT && uid != AID_SHELL) {
         return PERMISSION_DENIED;
     }
 
-    const int argCount = args.size();
-    if (argCount >= 1) {
+    Vector<String8> utf8Args;
+    utf8Args.setCapacity(argc);
+    for (uint32_t i = 0; i < argc; i++) {
+        utf8Args.push(String8(argv[i]));
+    }
+
+    if (argc >= 1) {
         // adb shell cmd stats config ...
-        if (!args[0].compare(String8("config"))) {
-            return cmd_config(in, out, err, args);
+        if (!utf8Args[0].compare(String8("config"))) {
+            return cmd_config(in, out, err, utf8Args);
         }
 
-        if (!args[0].compare(String8("print-uid-map"))) {
-            return cmd_print_uid_map(out, args);
+        if (!utf8Args[0].compare(String8("print-uid-map"))) {
+            return cmd_print_uid_map(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("dump-report"))) {
-            return cmd_dump_report(out, args);
+        if (!utf8Args[0].compare(String8("dump-report"))) {
+            return cmd_dump_report(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
-            return cmd_print_pulled_metrics(out, args);
+        if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) {
+            return cmd_print_pulled_metrics(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("send-broadcast"))) {
-            return cmd_trigger_broadcast(out, args);
+        if (!utf8Args[0].compare(String8("send-broadcast"))) {
+            return cmd_trigger_broadcast(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("print-stats"))) {
-            return cmd_print_stats(out, args);
+        if (!utf8Args[0].compare(String8("print-stats"))) {
+            return cmd_print_stats(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("meminfo"))) {
+        if (!utf8Args[0].compare(String8("meminfo"))) {
             return cmd_dump_memory_info(out);
         }
 
-        if (!args[0].compare(String8("write-to-disk"))) {
+        if (!utf8Args[0].compare(String8("write-to-disk"))) {
             return cmd_write_data_to_disk(out);
         }
 
-        if (!args[0].compare(String8("log-app-breadcrumb"))) {
-            return cmd_log_app_breadcrumb(out, args);
+        if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) {
+            return cmd_log_app_breadcrumb(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("log-binary-push"))) {
-            return cmd_log_binary_push(out, args);
+        if (!utf8Args[0].compare(String8("log-binary-push"))) {
+            return cmd_log_binary_push(out, utf8Args);
         }
 
-        if (!args[0].compare(String8("clear-puller-cache"))) {
+        if (!utf8Args[0].compare(String8("clear-puller-cache"))) {
             return cmd_clear_puller_cache(out);
         }
 
-        if (!args[0].compare(String8("print-logs"))) {
-            return cmd_print_logs(out, args);
+        if (!utf8Args[0].compare(String8("print-logs"))) {
+            return cmd_print_logs(out, utf8Args);
         }
-        if (!args[0].compare(String8("send-active-configs"))) {
-            return cmd_trigger_active_config_broadcast(out, args);
+
+        if (!utf8Args[0].compare(String8("send-active-configs"))) {
+            return cmd_trigger_active_config_broadcast(out, utf8Args);
         }
-        if (!args[0].compare(String8("data-subscribe"))) {
+
+        if (!utf8Args[0].compare(String8("data-subscribe"))) {
             {
                 std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
                 if (mShellSubscriber == nullptr) {
@@ -415,14 +359,10 @@
                 }
             }
             int timeoutSec = -1;
-            if (argCount >= 2) {
-                timeoutSec = atoi(args[1].c_str());
+            if (argc >= 2) {
+                timeoutSec = atoi(utf8Args[1].c_str());
             }
-            if (resultReceiver == nullptr) {
-                ALOGI("Null resultReceiver given, no subscription will be started");
-                return UNEXPECTED_NULL;
-            }
-            mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
+            mShellSubscriber->startNewSubscription(in, out, timeoutSec);
             return NO_ERROR;
         }
     }
@@ -452,9 +392,11 @@
     dprintf(out, "  PKG           Optional package name to print the uids of the package\n");
     dprintf(out, "\n");
     dprintf(out, "\n");
-    dprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
+    dprintf(out, "usage: adb shell cmd stats pull-source ATOM_TAG [PACKAGE] \n");
     dprintf(out, "\n");
-    dprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
+    dprintf(out, "  Prints the output of a pulled atom\n");
+    dprintf(out, "  UID           The atom to pull\n");
+    dprintf(out, "  PACKAGE       The package to pull from. Default is AID_SYSTEM\n");
     dprintf(out, "\n");
     dprintf(out, "\n");
     dprintf(out, "usage: adb shell cmd stats write-to-disk \n");
@@ -552,7 +494,7 @@
     const int argCount = args.size();
     if (argCount == 2) {
         // Automatically pick the UID
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         name.assign(args[1].c_str(), args[1].size());
         good = true;
     } else if (argCount == 3) {
@@ -568,18 +510,17 @@
         return UNKNOWN_ERROR;
     }
     ConfigKey key(uid, StrToInt64(name));
-    auto receiver = mConfigManager->GetConfigReceiver(key);
-    sp<IStatsCompanionService> sc = getStatsCompanionService();
-    if (sc == nullptr) {
-        VLOG("Could not access statsCompanion");
-    } else if (receiver == nullptr) {
-        VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str())
-    } else {
-        sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+    shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+    if (receiver == nullptr) {
+        VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str());
+        return UNKNOWN_ERROR;
+    } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) {
         VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
              args[2].c_str());
+    } else {
+        VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str());
+        return UNKNOWN_ERROR;
     }
-
     return NO_ERROR;
 }
 
@@ -589,7 +530,7 @@
     vector<int64_t> configIds;
     if (argCount == 1) {
         // Automatically pick the uid and send a broadcast that has no active configs.
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         mProcessor->GetActiveConfigs(uid, configIds);
     } else {
         int curArg = 1;
@@ -603,7 +544,7 @@
             }
             curArg++;
         } else {
-            uid = IPCThreadState::self()->getCallingUid();
+            uid = AIBinder_getCallingUid();
         }
         if (curArg == argCount || args[curArg] != "--configs") {
             VLOG("Reached end of args, or specify configs not set. Sending actual active configs,");
@@ -623,15 +564,15 @@
             }
         }
     }
-    auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
-    sp<IStatsCompanionService> sc = getStatsCompanionService();
-    if (sc == nullptr) {
-        VLOG("Could not access statsCompanion");
-    } else if (receiver == nullptr) {
+    shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
+    if (receiver == nullptr) {
         VLOG("Could not find receiver for uid %d", uid);
-    } else {
-        sc->sendActiveConfigsChangedBroadcast(receiver, configIds);
+        return UNKNOWN_ERROR;
+    } else if (receiver->sendActiveConfigsChangedBroadcast(configIds).isOk()) {
         VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid);
+    } else {
+        VLOG("StatsService::trigger active configs changed broadcast failed for uid %d", uid);
+        return UNKNOWN_ERROR;
     }
     return NO_ERROR;
 }
@@ -646,7 +587,7 @@
 
             if (argCount == 3) {
                 // Automatically pick the UID
-                uid = IPCThreadState::self()->getCallingUid();
+                uid = AIBinder_getCallingUid();
                 name.assign(args[2].c_str(), args[2].size());
                 good = true;
             } else if (argCount == 4) {
@@ -729,7 +670,7 @@
         }
         if (argCount == 2) {
             // Automatically pick the UID
-            uid = IPCThreadState::self()->getCallingUid();
+            uid = AIBinder_getCallingUid();
             name.assign(args[1].c_str(), args[1].size());
             good = true;
         } else if (argCount == 3) {
@@ -821,7 +762,7 @@
     const int argCount = args.size();
     if (argCount == 3) {
         // Automatically pick the UID
-        uid = IPCThreadState::self()->getCallingUid();
+        uid = AIBinder_getCallingUid();
         label = atoi(args[1].c_str());
         state = atoi(args[2].c_str());
         good = true;
@@ -837,7 +778,8 @@
     }
     if (good) {
         dprintf(out, "Logging AppBreadcrumbReported(%d, %d, %d) to statslog.\n", uid, label, state);
-        android::util::stats_write(android::util::APP_BREADCRUMB_REPORTED, uid, label, state);
+        android::os::statsd::util::stats_write(
+                android::os::statsd::util::APP_BREADCRUMB_REPORTED, uid, label, state);
     } else {
         print_cmd_help(out);
         return UNKNOWN_ERROR;
@@ -852,18 +794,8 @@
         dprintf(out, "Incorrect number of argument supplied\n");
         return UNKNOWN_ERROR;
     }
-    android::String16 trainName = android::String16(args[1].c_str());
+    string trainName = string(args[1].c_str());
     int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
-    int options = 0;
-    if (args[3] == "1") {
-        options = options | IStatsManager::FLAG_REQUIRE_STAGING;
-    }
-    if (args[4] == "1") {
-        options = options | IStatsManager::FLAG_ROLLBACK_ENABLED;
-    }
-    if (args[5] == "1") {
-        options = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    }
     int32_t state = atoi(args[6].c_str());
     vector<int64_t> experimentIds;
     if (argCount == 8) {
@@ -874,14 +806,30 @@
         }
     }
     dprintf(out, "Logging BinaryPushStateChanged\n");
-    sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds);
+    vector<uint8_t> experimentIdBytes;
+    writeExperimentIdsToProto(experimentIds, &experimentIdBytes);
+    LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0);
+    mProcessor->OnLogEvent(&event);
     return NO_ERROR;
 }
 
 status_t StatsService::cmd_print_pulled_metrics(int out, const Vector<String8>& args) {
     int s = atoi(args[1].c_str());
-    vector<shared_ptr<LogEvent> > stats;
-    if (mPullerManager->Pull(s, &stats)) {
+    vector<int32_t> uids;
+    if (args.size() > 2) {
+        string package = string(args[2].c_str());
+        auto it = UidMap::sAidToUidMapping.find(package);
+        if (it != UidMap::sAidToUidMapping.end()) {
+            uids.push_back(it->second);
+        } else {
+            set<int32_t> uids_set = mUidMap->getAppUid(package);
+            uids.insert(uids.end(), uids_set.begin(), uids_set.end());
+        }
+    } else {
+        uids.push_back(AID_SYSTEM);
+    }
+    vector<shared_ptr<LogEvent>> stats;
+    if (mPullerManager->Pull(s, uids, getElapsedRealtimeNs(), &stats)) {
         for (const auto& it : stats) {
             dprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
         }
@@ -905,10 +853,9 @@
 }
 
 status_t StatsService::cmd_clear_puller_cache(int out) {
-    IPCThreadState* ipc = IPCThreadState::self();
     VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i",
-            ipc->getCallingPid(), ipc->getCallingUid());
-    if (checkCallingPermission(String16(kPermissionDump))) {
+            AIBinder_getCallingPid(), AIBinder_getCallingUid());
+    if (checkPermission(kPermissionDump)) {
         int cleared = mPullerManager->ForceClearPullerCache();
         dprintf(out, "Puller removed %d cached data!\n", cleared);
         return NO_ERROR;
@@ -918,10 +865,9 @@
 }
 
 status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) {
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", ipc->getCallingPid(),
-         ipc->getCallingUid());
-    if (checkCallingPermission(String16(kPermissionDump))) {
+    VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", AIBinder_getCallingPid(),
+         AIBinder_getCallingUid());
+    if (checkPermission(kPermissionDump)) {
         bool enabled = true;
         if (args.size() >= 2) {
             enabled = atoi(args[1].c_str()) != 0;
@@ -952,24 +898,24 @@
     }
     uid = goodUid;
 
-    int32_t callingUid = IPCThreadState::self()->getCallingUid();
+    int32_t callingUid = AIBinder_getCallingUid();
     return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids.
             || (callingUid == goodUid) // Anyone can 'impersonate' themselves.
             || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL.
 }
 
-Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) {
+Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
     ENFORCE_UID(AID_SYSTEM);
     // Read stream into buffer.
     string buffer;
     if (!android::base::ReadFdToString(fd.get(), &buffer)) {
-        return exception(Status::EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
+        return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
     }
 
     // Parse buffer.
     UidData uidData;
     if (!uidData.ParseFromString(buffer)) {
-        return exception(Status::EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
+        return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
     }
 
     vector<String16> versionStrings;
@@ -1000,24 +946,31 @@
                        packageNames,
                        installers);
 
+    mBootCompleteTrigger.markComplete(kUidMapReceivedTag);
     VLOG("StatsService::informAllUidData UidData proto parsed successfully.");
     return Status::ok();
 }
 
-Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version,
-                                      const String16& version_string, const String16& installer) {
+Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version,
+                                      const string& versionString, const string& installer) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
-    mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer);
+    String16 utf16App = String16(app.c_str());
+    String16 utf16VersionString = String16(versionString.c_str());
+    String16 utf16Installer = String16(installer.c_str());
+
+    mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString,
+                       utf16Installer);
     return Status::ok();
 }
 
-Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
+Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackageRemoved was called");
-    mUidMap->removeApp(getElapsedRealtimeNs(), app, uid);
+    String16 utf16App = String16(app.c_str());
+    mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid);
     mConfigManager->RemoveConfigs(uid);
     return Status::ok();
 }
@@ -1077,11 +1030,12 @@
     VLOG("StatsService::informDeviceShutdown");
     mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST);
     mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+    mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
     return Status::ok();
 }
 
 void StatsService::sayHiToStatsCompanion() {
-    sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+    shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion != nullptr) {
         VLOG("Telling statsCompanion that statsd is ready");
         statsCompanion->statsdReady();
@@ -1094,24 +1048,32 @@
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::statsCompanionReady was called");
-    sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+    shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion == nullptr) {
-        return Status::fromExceptionCode(
-                Status::EX_NULL_POINTER,
-                "statscompanion unavailable despite it contacting statsd!");
+        return exception(EX_NULL_POINTER,
+                         "StatsCompanion unavailable despite it contacting statsd.");
     }
     VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
-    IInterface::asBinder(statsCompanion)->linkToDeath(this);
+    AIBinder_linkToDeath(statsCompanion->asBinder().get(),
+                         mStatsCompanionServiceDeathRecipient.get(), this);
     mPullerManager->SetStatsCompanionService(statsCompanion);
     mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion);
     mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion);
-    SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
+    return Status::ok();
+}
+
+Status StatsService::bootCompleted() {
+    ENFORCE_UID(AID_SYSTEM);
+
+    VLOG("StatsService::bootCompleted was called");
+    mBootCompleteTrigger.markComplete(kBootCompleteTag);
     return Status::ok();
 }
 
 void StatsService::Startup() {
     mConfigManager->Startup();
     mProcessor->LoadActiveConfigsFromDisk();
+    mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs());
 }
 
 void StatsService::Terminate() {
@@ -1119,6 +1081,7 @@
     if (mProcessor != nullptr) {
         mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST);
         mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+        mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
     }
 }
 
@@ -1130,12 +1093,11 @@
     }
 }
 
-Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    VLOG("StatsService::getData with Uid %i", callingUid);
+    ConfigKey configKey(callingUid, key);
     // The dump latency does not matter here since we do not include the current bucket, we do not
     // need to pull any new data anyhow.
     mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
@@ -1143,27 +1105,21 @@
     return Status::ok();
 }
 
-Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getMetadata(vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
-         ipc->getCallingUid());
     StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
     return Status::ok();
 }
 
 Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
-                                      const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                      const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
+    if (addConfigurationChecked(callingUid, key, config)) {
         return Status::ok();
     } else {
-        ALOGE("Could not parse malformatted StatsdConfig");
-        return Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT,
-                                         "config does not correspond to a StatsdConfig proto");
+        return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig.");
     }
 }
 
@@ -1179,23 +1135,21 @@
     return true;
 }
 
-Status StatsService::removeDataFetchOperation(int64_t key, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), key);
+Status StatsService::removeDataFetchOperation(int64_t key,
+                                              const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
+    ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfigReceiver(configKey);
     return Status::ok();
 }
 
 Status StatsService::setDataFetchOperation(int64_t key,
-                                           const sp<android::IBinder>& intentSender,
-                                           const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                           const shared_ptr<IPendingIntentRef>& pir,
+                                           const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), key);
-    mConfigManager->SetConfigReceiver(configKey, intentSender);
+    ConfigKey configKey(callingUid, key);
+    mConfigManager->SetConfigReceiver(configKey, pir);
     if (StorageManager::hasConfigMetricsReport(configKey)) {
         VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk",
              configKey.ToString().c_str());
@@ -1204,301 +1158,170 @@
     return Status::ok();
 }
 
-Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
-                                                      const String16& packageName,
+Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
+                                                      const int32_t callingUid,
                                                       vector<int64_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    int uid = ipc->getCallingUid();
-    mConfigManager->SetActiveConfigsChangedReceiver(uid, intentSender);
+    mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir);
     if (output != nullptr) {
-        mProcessor->GetActiveConfigs(uid, *output);
+        mProcessor->GetActiveConfigs(callingUid, *output);
     } else {
         ALOGW("StatsService::setActiveConfigsChanged output was nullptr");
     }
     return Status::ok();
 }
 
-Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid());
+    mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid);
     return Status::ok();
 }
 
-Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfig(configKey);
-    SubscriberReporter::getInstance().removeConfig(configKey);
     return Status::ok();
 }
 
 Status StatsService::setBroadcastSubscriber(int64_t configId,
                                             int64_t subscriberId,
-                                            const sp<android::IBinder>& intentSender,
-                                            const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                            const shared_ptr<IPendingIntentRef>& pir,
+                                            const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::setBroadcastSubscriber called.");
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), configId);
+    ConfigKey configKey(callingUid, configId);
     SubscriberReporter::getInstance()
-            .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+            .setBroadcastSubscriber(configKey, subscriberId, pir);
     return Status::ok();
 }
 
 Status StatsService::unsetBroadcastSubscriber(int64_t configId,
                                               int64_t subscriberId,
-                                              const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                              const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::unsetBroadcastSubscriber called.");
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), configId);
+    ConfigKey configKey(callingUid, configId);
     SubscriberReporter::getInstance()
             .unsetBroadcastSubscriber(configKey, subscriberId);
     return Status::ok();
 }
 
-Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) {
-    // Permission check not necessary as it's meant for applications to write to
-    // statsd.
-    android::util::stats_write(util::APP_BREADCRUMB_REPORTED,
-                               IPCThreadState::self()->getCallingUid(), label,
-                               state);
+Status StatsService::allPullersFromBootRegistered() {
+    ENFORCE_UID(AID_SYSTEM);
+
+    VLOG("StatsService::allPullersFromBootRegistered was called");
+    mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag);
     return Status::ok();
 }
 
-Status StatsService::registerPullerCallback(int32_t atomTag,
-        const sp<android::os::IStatsPullerCallback>& pullerCallback,
-        const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
-    VLOG("StatsService::registerPullerCallback called.");
-    mPullerManager->RegisterPullerCallback(atomTag, pullerCallback);
+Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis,
+                                              int64_t timeoutMillis,
+                                              const std::vector<int32_t>& additiveFields,
+                                              const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    ENFORCE_UID(AID_SYSTEM);
+    VLOG("StatsService::registerPullAtomCallback called.");
+    mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis),
+                                             MillisToNano(timeoutMillis), additiveFields,
+                                             pullerCallback);
     return Status::ok();
 }
 
-Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
-    VLOG("StatsService::unregisterPullerCallback called.");
-    mPullerManager->UnregisterPullerCallback(atomTag);
+Status StatsService::registerNativePullAtomCallback(
+        int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
+        const std::vector<int32_t>& additiveFields,
+        const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    if (!checkPermission(kPermissionRegisterPullAtom)) {
+        return exception(
+                EX_SECURITY,
+                StringPrintf("Uid %d does not have the %s permission when registering atom %d",
+                             AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag));
+    }
+    VLOG("StatsService::registerNativePullAtomCallback called.");
+    int32_t uid = AIBinder_getCallingUid();
+    mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis),
+                                             MillisToNano(timeoutMillis), additiveFields,
+                                             pullerCallback);
     return Status::ok();
 }
 
-Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
-                                                    const int64_t trainVersionCodeIn,
-                                                    const int options,
-                                                    const int32_t state,
-                                                    const std::vector<int64_t>& experimentIdsIn) {
-    // Note: We skip the usage stats op check here since we do not have a package name.
-    // This is ok since we are overloading the usage_stats permission.
-    // This method only sends data, it does not receive it.
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    // Root, system, and shell always have access
-    if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) {
-        // Caller must be granted these permissions
-        if (!checkCallingPermission(String16(kPermissionDump))) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionDump));
-        }
-        if (!checkCallingPermission(String16(kPermissionUsage))) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionUsage));
-        }
-    }
-
-    bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfoOnDisk;
-    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
-
-    bool resetExperimentIds = false;
-    int64_t trainVersionCode = trainVersionCodeIn;
-    std::string trainNameUtf8 = std::string(String8(trainNameIn).string());
-    if (readTrainInfoSuccess) {
-        // Keep the old train version if we received an empty version.
-        if (trainVersionCodeIn == -1) {
-            trainVersionCode = trainInfoOnDisk.trainVersionCode;
-        } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) {
-        // Reset experiment ids if we receive a new non-empty train version.
-            resetExperimentIds = true;
-        }
-
-        // Keep the old train name if we received an empty train name.
-        if (trainNameUtf8.size() == 0) {
-            trainNameUtf8 = trainInfoOnDisk.trainName;
-        } else if (trainNameUtf8 != trainInfoOnDisk.trainName) {
-            // Reset experiment ids if we received a new valid train name.
-            resetExperimentIds = true;
-        }
-
-        // Reset if we received a different experiment id.
-        if (!experimentIdsIn.empty() &&
-                (trainInfoOnDisk.experimentIds.empty() ||
-                 experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) {
-            resetExperimentIds = true;
-        }
-    }
-
-    // Find the right experiment IDs
-    std::vector<int64_t> experimentIds;
-    if (resetExperimentIds || !readTrainInfoSuccess) {
-        experimentIds = experimentIdsIn;
-    } else {
-        experimentIds = trainInfoOnDisk.experimentIds;
-    }
-
-    if (!experimentIds.empty()) {
-        int64_t firstId = experimentIds[0];
-        switch (state) {
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
-                experimentIds.push_back(firstId + 1);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
-                experimentIds.push_back(firstId + 2);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
-                experimentIds.push_back(firstId + 3);
-                break;
-        }
-    }
-
-    // Flatten the experiment IDs to proto
-    vector<uint8_t> experimentIdsProtoBuffer;
-    writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer);
-    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
-
-    userid_t userId = multiuser_get_user_id(uid);
-    bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING;
-    bool rollbackEnabled = options & IStatsManager::FLAG_ROLLBACK_ENABLED;
-    bool requiresLowLatencyMonitor = options & IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
-                   requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
-    mProcessor->OnLogEvent(&event);
+Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) {
+    ENFORCE_UID(AID_SYSTEM);
+    VLOG("StatsService::unregisterPullAtomCallback called.");
+    mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
     return Status::ok();
 }
 
-Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn,
-                                                      const android::String16& packageNameIn,
-                                                      const int64_t packageVersionCodeIn,
-                                                      const int32_t rollbackReasonIn,
-                                                      const android::String16&
-                                                       failingPackageNameIn) {
-    // Note: We skip the usage stats op check here since we do not have a package name.
-    // This is ok since we are overloading the usage_stats permission.
-    // This method only sends data, it does not receive it.
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    // Root, system, and shell always have access
-    if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) {
-        // Caller must be granted these permissions
-        if (!checkCallingPermission(String16(kPermissionDump))) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionDump));
-        }
-        if (!checkCallingPermission(String16(kPermissionUsage))) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionUsage));
-        }
+Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+    if (!checkPermission(kPermissionRegisterPullAtom)) {
+        return exception(
+                EX_SECURITY,
+                StringPrintf("Uid %d does not have the %s permission when unregistering atom %d",
+                             AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag));
     }
-
-    android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED,
-            rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn,
-            rollbackReasonIn, String8(failingPackageNameIn).string());
-
-    // Fast return to save disk read.
-    if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS
-            && rollbackTypeIn !=
-                    android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE) {
-        return Status::ok();
-    }
-
-    bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfoOnDisk;
-    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
-
-    if (!readTrainInfoSuccess) {
-        return Status::ok();
-    }
-    std::vector<int64_t> experimentIds = trainInfoOnDisk.experimentIds;
-    if (experimentIds.empty()) {
-        return Status::ok();
-    }
-    switch (rollbackTypeIn) {
-        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
-            experimentIds.push_back(experimentIds[0] + 4);
-            break;
-        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
-            experimentIds.push_back(experimentIds[0] + 5);
-            break;
-    }
-    StorageManager::writeTrainInfo(trainInfoOnDisk.trainVersionCode, trainInfoOnDisk.trainName,
-            trainInfoOnDisk.status, experimentIds);
+    VLOG("StatsService::unregisterNativePullAtomCallback called.");
+    int32_t uid = AIBinder_getCallingUid();
+    mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
     return Status::ok();
 }
 
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    // Caller must be granted these permissions
-    if (!checkCallingPermission(String16(kPermissionDump))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionDump));
-    }
-    if (!checkCallingPermission(String16(kPermissionUsage))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage));
-    }
+    ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
+    experimentIdsOut->clear();
     // Read the latest train info
-    InstallTrainInfo trainInfo;
-    if (!StorageManager::readTrainInfo(trainInfo)) {
+    vector<InstallTrainInfo> trainInfoList = StorageManager::readAllTrainInfo();
+    if (trainInfoList.empty()) {
         // No train info means no experiment IDs, return an empty list
-        experimentIdsOut->clear();
         return Status::ok();
     }
 
     // Copy the experiment IDs to the out vector
-    experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end());
+    for (InstallTrainInfo& trainInfo : trainInfoList) {
+        experimentIdsOut->insert(experimentIdsOut->end(),
+                                 trainInfo.experimentIds.begin(),
+                                 trainInfo.experimentIds.end());
+    }
     return Status::ok();
 }
 
-void StatsService::binderDied(const wp <IBinder>& who) {
+void StatsService::statsCompanionServiceDied(void* cookie) {
+    auto thiz = static_cast<StatsService*>(cookie);
+    thiz->statsCompanionServiceDiedImpl();
+}
+
+void StatsService::statsCompanionServiceDiedImpl() {
     ALOGW("statscompanion service died");
     StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
     if (mProcessor != nullptr) {
         ALOGW("Reset statsd upon system server restarts.");
         int64_t systemServerRestartNs = getElapsedRealtimeNs();
-        ProtoOutputStream proto;
+        ProtoOutputStream activeConfigsProto;
         mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs,
-                STATSCOMPANION_DIED, &proto);
-
+                STATSCOMPANION_DIED, &activeConfigsProto);
+        metadata::StatsMetadataList metadataList;
+        mProcessor->WriteMetadataToProto(getWallClockNs(),
+                systemServerRestartNs, &metadataList);
         mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST);
         mProcessor->resetConfigs();
 
         std::string serializedActiveConfigs;
-        if (proto.serializeToString(&serializedActiveConfigs)) {
+        if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) {
             ActiveConfigList activeConfigs;
             if (activeConfigs.ParseFromString(serializedActiveConfigs)) {
                 mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs);
             }
         }
+        mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs);
     }
     mAnomalyAlarmMonitor->setStatsCompanionService(nullptr);
     mPeriodicAlarmMonitor->setStatsCompanionService(nullptr);
-    SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
     mPullerManager->SetStatsCompanionService(nullptr);
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 991a205..324ffbd 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -17,7 +17,14 @@
 #ifndef STATS_SERVICE_H
 #define STATS_SERVICE_H
 
+#include <aidl/android/os/BnStatsd.h>
+#include <aidl/android/os/IPendingIntentRef.h>
+#include <aidl/android/os/IPullAtomCallback.h>
 #include <gtest/gtest_prod.h>
+#include <utils/Looper.h>
+
+#include <mutex>
+
 #include "StatsLogProcessor.h"
 #include "anomaly/AlarmMonitor.h"
 #include "config/ConfigManager.h"
@@ -26,32 +33,25 @@
 #include "packages/UidMap.h"
 #include "shell/ShellSubscriber.h"
 #include "statscompanion_util.h"
-
-#include <android/frameworks/stats/1.0/IStats.h>
-#include <android/frameworks/stats/1.0/types.h>
-#include <android/os/BnStatsManager.h>
-#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsManager.h>
-#include <binder/IResultReceiver.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <utils/Looper.h>
-
-#include <mutex>
+#include "utils/MultiConditionTrigger.h"
 
 using namespace android;
-using namespace android::binder;
-using namespace android::frameworks::stats::V1_0;
 using namespace android::os;
 using namespace std;
 
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnStatsd;
+using aidl::android::os::IPendingIntentRef;
+using aidl::android::os::IPullAtomCallback;
+using ::ndk::ScopedAIBinder_DeathRecipient;
+using ::ndk::ScopedFileDescriptor;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
-using android::hardware::Return;
-
-class StatsService : public BnStatsManager,
-                     public IBinder::DeathRecipient {
+class StatsService : public BnStatsd {
 public:
     StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue);
     virtual ~StatsService();
@@ -59,21 +59,21 @@
     /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
     const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5;
 
-    virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
-    virtual status_t dump(int fd, const Vector<String16>& args);
-    virtual status_t command(int inFd, int outFd, int err, Vector<String8>& args,
-                             sp<IResultReceiver> resultReceiver);
+    virtual status_t dump(int fd, const char** args, uint32_t numArgs) override;
+    virtual status_t handleShellCommand(int in, int out, int err, const char** argv,
+                                        uint32_t argc) override;
 
     virtual Status systemRunning();
     virtual Status statsCompanionReady();
+    virtual Status bootCompleted();
     virtual Status informAnomalyAlarmFired();
     virtual Status informPollAlarmFired();
     virtual Status informAlarmForSubscriberTriggeringFired();
 
-    virtual Status informAllUidData(const ParcelFileDescriptor& fd);
-    virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
-                                    const String16& version_string, const String16& installer);
-    virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
+    virtual Status informAllUidData(const ScopedFileDescriptor& fd);
+    virtual Status informOnePackage(const string& app, int32_t uid, int64_t version,
+                                    const string& versionString, const string& installer);
+    virtual Status informOnePackageRemoved(const string& app, int32_t uid);
     virtual Status informDeviceShutdown();
 
     /**
@@ -95,15 +95,14 @@
      * Binder call for clients to request data for this configuration key.
      */
     virtual Status getData(int64_t key,
-                           const String16& packageName,
+                           const int32_t callingUid,
                            vector<uint8_t>* output) override;
 
 
     /**
      * Binder call for clients to get metadata across all configs in statsd.
      */
-    virtual Status getMetadata(const String16& packageName,
-                               vector<uint8_t>* output) override;
+    virtual Status getMetadata(vector<uint8_t>* output) override;
 
 
     /**
@@ -112,103 +111,92 @@
      */
     virtual Status addConfiguration(int64_t key,
                                     const vector<uint8_t>& config,
-                                    const String16& packageName) override;
+                                    const int32_t callingUid) override;
 
     /**
      * Binder call to let clients register the data fetch operation for a configuration.
      */
     virtual Status setDataFetchOperation(int64_t key,
-                                         const sp<android::IBinder>& intentSender,
-                                         const String16& packageName) override;
+                                         const shared_ptr<IPendingIntentRef>& pir,
+                                         const int32_t callingUid) override;
 
     /**
      * Binder call to remove the data fetch operation for the specified config key.
      */
     virtual Status removeDataFetchOperation(int64_t key,
-                                            const String16& packageName) override;
+                                            const int32_t callingUid) override;
 
     /**
      * Binder call to let clients register the active configs changed operation.
      */
-    virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
-                                                    const String16& packageName,
+    virtual Status setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
+                                                    const int32_t callingUid,
                                                     vector<int64_t>* output) override;
 
     /**
      * Binder call to remove the active configs changed operation for the specified package..
      */
-    virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override;
+    virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override;
     /**
      * Binder call to allow clients to remove the specified configuration.
      */
     virtual Status removeConfiguration(int64_t key,
-                                       const String16& packageName) override;
+                                       const int32_t callingUid) override;
 
     /**
-     * Binder call to associate the given config's subscriberId with the given intentSender.
-     * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+     * Binder call to associate the given config's subscriberId with the given pendingIntentRef.
      */
     virtual Status setBroadcastSubscriber(int64_t configId,
                                           int64_t subscriberId,
-                                          const sp<android::IBinder>& intentSender,
-                                          const String16& packageName) override;
+                                          const shared_ptr<IPendingIntentRef>& pir,
+                                          const int32_t callingUid) override;
 
     /**
-     * Binder call to unassociate the given config's subscriberId with any intentSender.
+     * Binder call to unassociate the given config's subscriberId with any pendingIntentRef.
      */
     virtual Status unsetBroadcastSubscriber(int64_t configId,
                                             int64_t subscriberId,
-                                            const String16& packageName) override;
+                                            const int32_t callingUid) override;
 
     /** Inform statsCompanion that statsd is ready. */
     virtual void sayHiToStatsCompanion();
 
     /**
-     * Binder call to get AppBreadcrumbReported atom.
+     * Binder call to notify statsd that all pullers from boot have been registered.
      */
-    virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
+    virtual Status allPullersFromBootRegistered();
 
     /**
-     * Binder call to register a callback function for a vendor pulled atom.
-     * Note: this atom must NOT have uid as a field.
+     * Binder call to register a callback function for a pulled atom.
      */
-    virtual Status registerPullerCallback(int32_t atomTag,
-        const sp<android::os::IStatsPullerCallback>& pullerCallback,
-        const String16& packageName) override;
+    virtual Status registerPullAtomCallback(
+            int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
+            const std::vector<int32_t>& additiveFields,
+            const shared_ptr<IPullAtomCallback>& pullerCallback) override;
 
     /**
-     * Binder call to unregister any existing callback function for a vendor pulled atom.
+     * Binder call to register a callback function for a pulled atom.
      */
-    virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override;
+    virtual Status registerNativePullAtomCallback(
+            int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
+            const std::vector<int32_t>& additiveFields,
+            const shared_ptr<IPullAtomCallback>& pullerCallback) override;
 
     /**
-     * Binder call to log BinaryPushStateChanged atom.
+     * Binder call to unregister any existing callback for the given uid and atom.
      */
-    virtual Status sendBinaryPushStateChangedAtom(
-            const android::String16& trainNameIn,
-            const int64_t trainVersionCodeIn,
-            const int options,
-            const int32_t state,
-            const std::vector<int64_t>& experimentIdsIn) override;
+    virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
 
     /**
-     * Binder call to log WatchdogRollbackOccurred atom.
+     * Binder call to unregister any existing callback for the given atom and calling uid.
      */
-    virtual Status sendWatchdogRollbackOccurredAtom(
-            const int32_t rollbackTypeIn,
-            const android::String16& packageNameIn,
-            const int64_t packageVersionCodeIn,
-            const int32_t rollbackReasonIn,
-            const android::String16& failingPackageNameIn) override;
+    virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
 
     /**
      * Binder call to get registered experiment IDs.
      */
     virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
 
-    /** IBinder::DeathRecipient */
-    virtual void binderDied(const wp<IBinder>& who) override;
-
 private:
     /**
      * Load system properties at init.
@@ -340,6 +328,17 @@
     void set_config(int uid, const string& name, const StatsdConfig& config);
 
     /**
+     * Death recipient callback that is called when StatsCompanionService dies.
+     * The cookie is a pointer to a StatsService object.
+     */
+    static void statsCompanionServiceDied(void* cookie);
+
+    /**
+     * Implementation of statsCompanionServiceDied.
+     */
+    void statsCompanionServiceDiedImpl();
+
+    /**
      * Tracks the uid <--> package name mapping.
      */
     sp<UidMap> mUidMap;
@@ -382,17 +381,27 @@
     mutable mutex mShellSubscriberMutex;
     std::shared_ptr<LogEventQueue> mEventQueue;
 
+    MultiConditionTrigger mBootCompleteTrigger;
+    static const inline string kBootCompleteTag = "BOOT_COMPLETE";
+    static const inline string kUidMapReceivedTag = "UID_MAP";
+    static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED";
+
+    ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
+
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_empty);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid);
     FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp);
+    FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit);
+    FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket);
+    FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
 };
diff --git a/cmds/statsd/src/annotations.h b/cmds/statsd/src/annotations.h
new file mode 100644
index 0000000..cf7f543
--- /dev/null
+++ b/cmds/statsd/src/annotations.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const uint8_t ANNOTATION_ID_IS_UID = 1;
+const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3;
+const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4;
+const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
+const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
+const uint8_t ANNOTATION_ID_STATE_NESTED = 8;
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
index bc36dad..b632d04 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
@@ -26,17 +26,19 @@
 
 AlarmMonitor::AlarmMonitor(
         uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
-        const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
-        const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm)
-    : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
+        const std::function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& updateAlarm,
+        const std::function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm)
+    : mRegisteredAlarmTimeSec(0),
+      mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
       mUpdateAlarm(updateAlarm),
       mCancelAlarm(cancelAlarm) {}
 
 AlarmMonitor::~AlarmMonitor() {}
 
-void AlarmMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+void AlarmMonitor::setStatsCompanionService(
+        shared_ptr<IStatsCompanionService> statsCompanionService) {
     std::lock_guard<std::mutex> lock(mLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+    shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
     if (statsCompanionService == nullptr) {
         VLOG("Erasing link to statsCompanionService");
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.h b/cmds/statsd/src/anomaly/AlarmMonitor.h
index 219695e..5c34e38 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.h
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.h
@@ -18,7 +18,7 @@
 
 #include "anomaly/indexed_priority_queue.h"
 
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
 
 #include <unordered_set>
@@ -26,7 +26,9 @@
 
 using namespace android;
 
-using android::os::IStatsCompanionService;
+using aidl::android::os::IStatsCompanionService;
+using std::function;
+using std::shared_ptr;
 using std::unordered_set;
 
 namespace android {
@@ -64,8 +66,9 @@
      * alarm.
      */
     AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
-                 const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
-                 const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm);
+                 const function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>&
+                         updateAlarm,
+                 const function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm);
     ~AlarmMonitor();
 
     /**
@@ -74,7 +77,7 @@
      * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
      * update IStatsCompanionService (until such time as it is set non-null).
      */
-    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+    void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
 
     /**
      * Adds the given alarm (reference) to the queue.
@@ -123,7 +126,7 @@
     /**
      * Binder interface for communicating with StatsCompanionService.
      */
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+    shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
 
     /**
      * Amount by which the soonest projected alarm must differ from
@@ -147,10 +150,10 @@
     int64_t secToMs(uint32_t timeSec);
 
     // Callback function to update the alarm via StatsCompanionService.
-    std::function<void(const sp<IStatsCompanionService>, int64_t)> mUpdateAlarm;
+    std::function<void(const shared_ptr<IStatsCompanionService>, int64_t)> mUpdateAlarm;
 
     // Callback function to cancel the alarm via StatsCompanionService.
-    std::function<void(const sp<IStatsCompanionService>)> mCancelAlarm;
+    std::function<void(const shared_ptr<IStatsCompanionService>)> mCancelAlarm;
 
 };
 
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.cpp b/cmds/statsd/src/anomaly/AlarmTracker.cpp
index a21327b..6d9beb8 100644
--- a/cmds/statsd/src/anomaly/AlarmTracker.cpp
+++ b/cmds/statsd/src/anomaly/AlarmTracker.cpp
@@ -23,7 +23,6 @@
 #include "stats_util.h"
 #include "storage/StorageManager.h"
 
-#include <statslog.h>
 #include <time.h>
 
 namespace android {
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.h b/cmds/statsd/src/anomaly/AlarmTracker.h
index 2fb3e3b..2da4a186 100644
--- a/cmds/statsd/src/anomaly/AlarmTracker.h
+++ b/cmds/statsd/src/anomaly/AlarmTracker.h
@@ -22,12 +22,9 @@
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alarm
 
-#include <android/os/IStatsCompanionService.h>
 #include <stdlib.h>
 #include <utils/RefBase.h>
 
-using android::os::IStatsCompanionService;
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 7ace44e..619752c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -18,14 +18,16 @@
 #include "Log.h"
 
 #include "AnomalyTracker.h"
-#include "subscriber_util.h"
 #include "external/Perfetto.h"
 #include "guardrail/StatsdStats.h"
+#include "metadata_util.h"
+#include "stats_log_util.h"
+#include "subscriber_util.h"
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
 #include <inttypes.h>
-#include <statslog.h>
+#include <statslog_statsd.h>
 #include <time.h>
 
 namespace android {
@@ -235,8 +237,8 @@
     StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
 
     // TODO(b/110564268): This should also take in the const MetricDimensionKey& key?
-    android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
-                               mConfigKey.GetId(), mAlert.id());
+    util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(),
+                      mConfigKey.GetId(), mAlert.id());
 }
 
 void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs,
@@ -262,6 +264,58 @@
     triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
 }
 
+bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs,
+                                               int64_t systemElapsedTimeNs,
+                                               metadata::AlertMetadata* alertMetadata) {
+    bool metadataWritten = false;
+
+    if (mRefractoryPeriodEndsSec.empty()) {
+        return false;
+    }
+
+    for (const auto& it: mRefractoryPeriodEndsSec) {
+        // Do not write the timestamp to disk if it has already expired
+        if (it.second < systemElapsedTimeNs / NS_PER_SEC) {
+            continue;
+        }
+
+        metadataWritten = true;
+        if (alertMetadata->alert_dim_keyed_data_size() == 0) {
+            alertMetadata->set_alert_id(mAlert.id());
+        }
+
+        metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data();
+        // We convert and write the refractory_end_sec to wall clock time because we do not know
+        // when statsd will start again.
+        int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) +
+                (it.second - systemElapsedTimeNs / NS_PER_SEC));
+
+        keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec);
+        writeMetricDimensionKeyToMetadataDimensionKey(
+                it.first, keyedData->mutable_dimension_key());
+    }
+
+    return metadataWritten;
+}
+
+void AnomalyTracker::loadAlertMetadata(
+        const metadata::AlertMetadata& alertMetadata,
+        int64_t currentWallClockTimeNs,
+        int64_t systemElapsedTimeNs) {
+    for (const metadata::AlertDimensionKeyedData& keyedData :
+            alertMetadata.alert_dim_keyed_data()) {
+        if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) {
+            // Do not update the timestamp if it has already expired.
+            continue;
+        }
+        MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto(
+                keyedData.dimension_key());
+        int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() -
+                currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC;
+        mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec;
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 794ee98..bf36a3b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -24,6 +24,7 @@
 #include "AlarmMonitor.h"
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"  // AlertMetadata
 #include "stats_util.h"  // HashableDimensionKey and DimToValMap
 
 namespace android {
@@ -112,6 +113,17 @@
         return; // The base AnomalyTracker class doesn't have alarms.
     }
 
+    // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata.
+    // Returns true if at least one element is written to alertMetadata.
+    bool writeAlertMetadataToProto(
+            int64_t currentWallClockTimeNs,
+            int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata);
+
+    void loadAlertMetadata(
+            const metadata::AlertMetadata& alertMetadata,
+            int64_t currentWallClockTimeNs,
+            int64_t systemElapsedTimeNs);
+
 protected:
     // For testing only.
     // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index 16c936c..ff5717e 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -23,45 +23,72 @@
 
 import "google/protobuf/descriptor.proto";
 
-enum StateField {
-    // Default value for fields that are not primary or exclusive state.
-    STATE_FIELD_UNSET = 0;
-    // Fields that represent the key that the state belongs to.
-    PRIMARY = 1;
-    // The field that represents the state. It's an exclusive state.
-    EXCLUSIVE = 2;
-}
-
-// Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE
-// exclusive state field, and any number of primary key fields.
-// For example,
-// message UidProcessStateChanged {
-//    optional int32 uid = 1 [(state_field_option).option = PRIMARY];
-//    optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE];
+// Used to annotate an atom that represents a state change. A state change atom must have exactly
+// ONE exclusive state field, and any number of primary key fields. For example, message
+// UidProcessStateChanged {
+//    optional int32 uid = 1 [(state_field_option).primary_field = true];
+//    optional android.app.ProcessStateEnum state =
+//            2 [(state_field_option).exclusive_state = true];
 //  }
-// Each of this UidProcessStateChanged atom represents a state change for a specific uid.
+// Each UidProcessStateChanged atom event represents a state change for a specific uid.
 // A new state automatically overrides the previous state.
 //
-// If the atom has 2 or more primary fields, it means the combination of the primary fields are
-// the primary key.
+// If the atom has 2 or more primary fields, it means the combination of the
+// primary fields are the primary key.
 // For example:
 // message ThreadStateChanged {
-//    optional int32 pid = 1  [(state_field_option).option = PRIMARY];
-//    optional int32 tid = 2  [(state_field_option).option = PRIMARY];
-//    optional int32 state = 3 [(state_field_option).option = EXCLUSIVE];
+//    optional int32 pid = 1  [(state_field_option).primary_field = true];
+//    optional int32 tid = 2  [(state_field_option).primary_field = true];
+//    optional int32 state = 3 [(state_field_option).exclusive_state = true];
 // }
 //
 // Sometimes, there is no primary key field, when the state is GLOBAL.
 // For example,
-//
 // message ScreenStateChanged {
-//    optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE];
+//    optional android.view.DisplayStateEnum state =
+//          1 [(state_field_option).exclusive_state = true];
 // }
 //
-// Only fields of primary types can be annotated. AttributionNode cannot be primary keys (and they
-// usually are not).
+// For state atoms with attribution chain, sometimes the primary key is the first uid in the chain.
+// For example:
+// message AudioStateChanged {
+//   repeated AttributionNode attribution_node = 1
+//       [(stateFieldOption).primary_field_first_uid = true];
+//
+//    enum State {
+//      OFF = 0;
+//      ON = 1;
+//      // RESET indicates all audio stopped. Used when it (re)starts (e.g. after it crashes).
+//      RESET = 2;
+//    }
+//    optional State state = 2 [(stateFieldOption).exclusive_state = true];
+// }
 message StateAtomFieldOption {
-    optional StateField option = 1 [default = STATE_FIELD_UNSET];
+    // Fields that represent the key that the state belongs to.
+    // Used on simple proto fields. Do not use on attribution chains.
+    optional bool primary_field = 1 [default = false];
+
+    // The field that represents the state. It's an exclusive state.
+    optional bool exclusive_state = 2 [default = false];
+
+    // Used on an attribution chain field to indicate that the first uid is the
+    // primary field.
+    optional bool primary_field_first_uid = 3 [default = false];
+
+    // Note: We cannot annotate directly on the enums because many enums are imported from other
+    // proto files in the platform. proto-lite cc library does not support annotations unfortunately
+
+    // Knowing the default state value allows state trackers to remove entries that become the
+    // default state. If there is no default value specified, the default value is unknown, and all
+    // states will be tracked in memory.
+    optional int32 default_state_value = 4;
+
+    // A reset state signals all states go to default value. For example, BLE reset means all active
+    // BLE scans are to be turned off.
+    optional int32 trigger_state_reset_value = 5;
+
+    // If the state change needs to count nesting.
+    optional bool nested = 6 [default = true];
 }
 
 // Used to generate StatsLog.write APIs.
@@ -83,7 +110,7 @@
 
     optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC];
 
-    optional bool allow_from_any_uid = 50003 [default = false];
+    repeated string module = 50004;
 
-    optional string log_from_module = 50004;
-}
\ No newline at end of file
+    optional bool truncate_timestamp = 50005 [default = false];
+}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b8de3f0..ab1d3cb 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -24,6 +24,8 @@
 import "frameworks/base/core/proto/android/app/enums.proto";
 import "frameworks/base/core/proto/android/app/job/enums.proto";
 import "frameworks/base/core/proto/android/app/settings_enums.proto";
+import "frameworks/base/core/proto/android/app/media_output_enum.proto";
+import "frameworks/base/core/proto/android/app/tvsettings_enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/a2dp/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
@@ -46,18 +48,22 @@
 import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy.proto";
 import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enums.proto";
 import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto";
+import "frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto";
 import "frameworks/base/core/proto/android/stats/enums.proto";
 import "frameworks/base/core/proto/android/stats/intelligence/enums.proto";
 import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
 import "frameworks/base/core/proto/android/stats/location/location_enums.proto";
 import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto";
-import "frameworks/base/core/proto/android/stats/otaupdate/updateengine_enums.proto";
+import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto";
 import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto";
 import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
+import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
 import "frameworks/base/core/proto/android/telecomm/enums.proto";
 import "frameworks/base/core/proto/android/telephony/enums.proto";
 import "frameworks/base/core/proto/android/view/enums.proto";
 import "frameworks/base/core/proto/android/wifi/enums.proto";
+import "frameworks/base/core/proto/android/stats/textclassifier/textclassifier_enums.proto";
+import "frameworks/base/core/proto/android/stats/otaupdate/updateengine_enums.proto";
 
 /**
  * The primary atom class. This message defines all of the available
@@ -76,241 +82,258 @@
     // Pushed atoms start at 2.
     oneof pushed {
         // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
-        BleScanStateChanged ble_scan_state_changed = 2 [(log_from_module) = "bluetooth"];
-        ProcessStateChanged process_state_changed = 3;
-        BleScanResultReceived ble_scan_result_received = 4 [(log_from_module) = "bluetooth"];
-        SensorStateChanged sensor_state_changed = 5;
-        GpsScanStateChanged gps_scan_state_changed = 6;
-        SyncStateChanged sync_state_changed = 7;
-        ScheduledJobStateChanged scheduled_job_state_changed = 8;
-        ScreenBrightnessChanged screen_brightness_changed = 9;
-        WakelockStateChanged wakelock_state_changed = 10;
-        LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 11;
-        MobileRadioPowerStateChanged mobile_radio_power_state_changed = 12 [(log_from_module) = "framework"];
-        WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13 [(log_from_module) = "framework"];
-        ActivityManagerSleepStateChanged activity_manager_sleep_state_changed = 14;
-        MemoryFactorStateChanged memory_factor_state_changed = 15;
-        ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16;
-        CachedKillReported cached_kill_reported = 17;
-        ProcessMemoryStatReported process_memory_stat_reported = 18;
-        LauncherUIChanged launcher_event = 19;
-        BatterySaverModeStateChanged battery_saver_mode_state_changed = 20;
-        DeviceIdleModeStateChanged device_idle_mode_state_changed = 21;
-        DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22;
-        AudioStateChanged audio_state_changed = 23;
-        MediaCodecStateChanged media_codec_state_changed = 24;
-        CameraStateChanged camera_state_changed = 25;
-        FlashlightStateChanged flashlight_state_changed = 26;
-        UidProcessStateChanged uid_process_state_changed = 27;
-        ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
-        ScreenStateChanged screen_state_changed = 29;
-        BatteryLevelChanged battery_level_changed = 30;
-        ChargingStateChanged charging_state_changed = 31;
-        PluggedStateChanged plugged_state_changed = 32;
-        InteractiveStateChanged interactive_state_changed = 33;
+        BleScanStateChanged ble_scan_state_changed = 2
+                [(module) = "bluetooth", (module) = "statsdtest"];
+        ProcessStateChanged process_state_changed = 3 [(module) = "framework"];
+        BleScanResultReceived ble_scan_result_received = 4 [(module) = "bluetooth"];
+        SensorStateChanged sensor_state_changed =
+                5 [(module) = "framework", (module) = "statsdtest"];
+        GpsScanStateChanged gps_scan_state_changed = 6 [(module) = "framework"];
+        SyncStateChanged sync_state_changed = 7 [(module) = "framework", (module) = "statsdtest"];
+        ScheduledJobStateChanged scheduled_job_state_changed =
+                8 [(module) = "framework", (module) = "statsdtest"];
+        ScreenBrightnessChanged screen_brightness_changed =
+                9 [(module) = "framework", (module) = "statsdtest"];
+        WakelockStateChanged wakelock_state_changed =
+                10 [(module) = "framework", (module) = "statsdtest"];
+        LongPartialWakelockStateChanged long_partial_wakelock_state_changed =
+                11 [(module) = "framework"];
+        MobileRadioPowerStateChanged mobile_radio_power_state_changed =
+                12 [(module) = "framework", (truncate_timestamp) = true];
+        WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13 [(module) = "framework"];
+        ActivityManagerSleepStateChanged activity_manager_sleep_state_changed =
+                14 [(module) = "framework"];
+        MemoryFactorStateChanged memory_factor_state_changed = 15 [(module) = "framework"];
+        ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16 [(module) = "framework"];
+        CachedKillReported cached_kill_reported = 17 [(module) = "framework"];
+        ProcessMemoryStatReported process_memory_stat_reported = 18 [(module) = "framework"];
+        LauncherUIChanged launcher_event = 19 [(module) = "sysui"];
+        BatterySaverModeStateChanged battery_saver_mode_state_changed =
+                20 [(module) = "framework", (module) = "statsdtest"];
+        DeviceIdleModeStateChanged device_idle_mode_state_changed = 21 [(module) = "framework"];
+        DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22 [(module) = "framework"];
+        AudioStateChanged audio_state_changed =
+                23 [(module) = "framework", (truncate_timestamp) = true];
+        MediaCodecStateChanged media_codec_state_changed = 24 [(module) = "framework"];
+        CameraStateChanged camera_state_changed = 25 [(module) = "framework"];
+        FlashlightStateChanged flashlight_state_changed = 26 [(module) = "framework"];
+        UidProcessStateChanged uid_process_state_changed =
+                27 [(module) = "framework", (module) = "statsdtest"];
+        ProcessLifeCycleStateChanged process_life_cycle_state_changed =
+                28 [(module) = "framework", (module) = "statsdtest"];
+        ScreenStateChanged screen_state_changed =
+                29 [(module) = "framework", (module) = "statsdtest"];
+        BatteryLevelChanged battery_level_changed =
+                30 [(module) = "framework", (module) = "statsdtest"];
+        ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
+        PluggedStateChanged plugged_state_changed = 32
+                [(module) = "framework", (module) = "statsdtest"];
+        InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
         TouchEventReported touch_event_reported = 34;
-        WakeupAlarmOccurred wakeup_alarm_occurred = 35;
-        KernelWakeupReported kernel_wakeup_reported = 36;
-        WifiLockStateChanged wifi_lock_state_changed = 37;
-        WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
-        WifiScanStateChanged wifi_scan_state_changed = 39;
-        PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
-        SettingChanged setting_changed = 41;
-        ActivityForegroundStateChanged activity_foreground_state_changed = 42;
-        IsolatedUidChanged isolated_uid_changed = 43;
-        PacketWakeupOccurred packet_wakeup_occurred = 44 [(log_from_module) = "framework"];
-        WallClockTimeShifted wall_clock_time_shifted = 45;
-        AnomalyDetected anomaly_detected = 46;
-        AppBreadcrumbReported app_breadcrumb_reported = 47 [(allow_from_any_uid) = true];
-        AppStartOccurred app_start_occurred = 48;
-        AppStartCanceled app_start_canceled = 49;
-        AppStartFullyDrawn app_start_fully_drawn = 50;
-        LmkKillOccurred lmk_kill_occurred = 51 [(log_from_module) = "lmkd"];
-        PictureInPictureStateChanged picture_in_picture_state_changed = 52;
-        WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53;
-        LmkStateChanged lmk_state_changed = 54 [(log_from_module) = "lmkd"];
-        AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
-        ShutdownSequenceReported shutdown_sequence_reported = 56;
+        WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
+        KernelWakeupReported kernel_wakeup_reported = 36 [(module) = "framework"];
+        WifiLockStateChanged wifi_lock_state_changed = 37 [(module) = "wifi"];
+        WifiSignalStrengthChanged wifi_signal_strength_changed = 38 [(module) = "wifi"];
+        WifiScanStateChanged wifi_scan_state_changed = 39 [(module) = "wifi"];
+        PhoneSignalStrengthChanged phone_signal_strength_changed =
+                40 [(module) = "framework", (truncate_timestamp) = true];
+        SettingChanged setting_changed = 41 [(module) = "framework"];
+        ActivityForegroundStateChanged activity_foreground_state_changed =
+                42 [(module) = "framework", (module) = "statsdtest"];
+        IsolatedUidChanged isolated_uid_changed =
+                43 [(module) = "framework", (module) = "statsd", (module) = "statsdtest"];
+        PacketWakeupOccurred packet_wakeup_occurred = 44 [(module) = "framework"];
+        WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"];
+        AnomalyDetected anomaly_detected = 46 [(module) = "statsd"];
+        AppBreadcrumbReported app_breadcrumb_reported = 47 [(module) = "statsd"];
+        AppStartOccurred app_start_occurred = 48 [(module) = "framework", (module) = "statsdtest"];
+        AppStartCanceled app_start_canceled = 49 [(module) = "framework"];
+        AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"];
+        LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"];
+        PictureInPictureStateChanged picture_in_picture_state_changed = 52 [(module) = "framework"];
+        WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"];
+        LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"];
+        AppStartMemoryStateCaptured app_start_memory_state_captured = 55 [(module) = "framework"];
+        ShutdownSequenceReported shutdown_sequence_reported = 56 [(module) = "framework"];
         BootSequenceReported boot_sequence_reported = 57;
-        DaveyOccurred davey_occurred = 58 [(allow_from_any_uid) = true];
-        OverlayStateChanged overlay_state_changed = 59;
-        ForegroundServiceStateChanged foreground_service_state_changed = 60;
-        CallStateChanged call_state_changed = 61;
-        KeyguardStateChanged keyguard_state_changed = 62;
-        KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63;
-        KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64;
-        AppDied app_died = 65;
-        ResourceConfigurationChanged resource_configuration_changed = 66;
-        BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
+        DaveyOccurred davey_occurred = 58 [(module) = "statsd"];
+        OverlayStateChanged overlay_state_changed =
+                59 [(module) = "framework", (module) = "statsdtest"];
+        ForegroundServiceStateChanged foreground_service_state_changed
+                = 60 [(module) = "framework"];
+        CallStateChanged call_state_changed =
+                61 [(module) = "telecom", (truncate_timestamp) = true];
+        KeyguardStateChanged keyguard_state_changed = 62 [(module) = "sysui"];
+        KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63 [(module) = "sysui"];
+        KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64 [(module) = "sysui"];
+        AppDied app_died = 65 [(module) = "framework"];
+        ResourceConfigurationChanged resource_configuration_changed = 66 [(module) = "framework"];
+        BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67 [(module) = "framework"];
         BluetoothConnectionStateChanged bluetooth_connection_state_changed =
-                68 [(log_from_module) = "bluetooth"];
-        GpsSignalQualityChanged gps_signal_quality_changed = 69;
-        UsbConnectorStateChanged usb_connector_state_changed = 70;
+                68 [(module) = "bluetooth"];
+        GpsSignalQualityChanged gps_signal_quality_changed = 69 [(module) = "framework"];
+        UsbConnectorStateChanged usb_connector_state_changed = 70 [(module) = "framework"];
         SpeakerImpedanceReported speaker_impedance_reported = 71;
         HardwareFailed hardware_failed = 72;
         PhysicalDropDetected physical_drop_detected = 73;
         ChargeCyclesReported charge_cycles_reported = 74;
-        MobileConnectionStateChanged mobile_connection_state_changed =
-                75 [(log_from_module) = "telephony"];
-        MobileRadioTechnologyChanged mobile_radio_technology_changed =
-                76 [(log_from_module) = "telephony"];
-        UsbDeviceAttached usb_device_attached = 77;
-        AppCrashOccurred app_crash_occurred = 78;
-        ANROccurred anr_occurred = 79;
-        WTFOccurred wtf_occurred = 80;
-        LowMemReported low_mem_reported = 81;
+        MobileConnectionStateChanged mobile_connection_state_changed = 75 [(module) = "telephony"];
+        MobileRadioTechnologyChanged mobile_radio_technology_changed = 76 [(module) = "telephony"];
+        UsbDeviceAttached usb_device_attached = 77 [(module) = "framework"];
+        AppCrashOccurred app_crash_occurred = 78 [(module) = "framework", (module) = "statsdtest"];
+        ANROccurred anr_occurred = 79 [(module) = "framework"];
+        WTFOccurred wtf_occurred = 80 [(module) = "framework"];
+        LowMemReported low_mem_reported = 81 [(module) = "framework"];
         GenericAtom generic_atom = 82;
-        KeyValuePairsAtom key_value_pairs_atom = 83 [(allow_from_any_uid) = true];
-        VibratorStateChanged vibrator_state_changed = 84;
-        DeferredJobStatsReported deferred_job_stats_reported = 85;
+        KeyValuePairsAtom key_value_pairs_atom = 83 [(module) = "framework", (module) = "statsd"];
+        VibratorStateChanged vibrator_state_changed = 84 [(module) = "framework"];
+        DeferredJobStatsReported deferred_job_stats_reported = 85 [(module) = "framework"];
         ThermalThrottlingStateChanged thermal_throttling = 86 [deprecated=true];
-        BiometricAcquired biometric_acquired = 87;
-        BiometricAuthenticated biometric_authenticated = 88;
-        BiometricErrorOccurred biometric_error_occurred = 89;
-        // Atom number 90 is available for use.
+        BiometricAcquired biometric_acquired = 87 [(module) = "framework"];
+        BiometricAuthenticated biometric_authenticated = 88 [(module) = "framework"];
+        BiometricErrorOccurred biometric_error_occurred = 89 [(module) = "framework"];
+        UiEventReported ui_event_reported = 90 [(module) = "framework", (module) = "sysui"];
         BatteryHealthSnapshot battery_health_snapshot = 91;
         SlowIo slow_io = 92;
         BatteryCausedShutdown battery_caused_shutdown = 93;
-        PhoneServiceStateChanged phone_service_state_changed = 94;
-        PhoneStateChanged phone_state_changed = 95;
+        PhoneServiceStateChanged phone_service_state_changed = 94 [(module) = "framework"];
+        PhoneStateChanged phone_state_changed = 95 [(module) = "framework"];
         UserRestrictionChanged user_restriction_changed = 96;
-        SettingsUIChanged settings_ui_changed = 97;
-        ConnectivityStateChanged connectivity_state_changed = 98;
+        SettingsUIChanged settings_ui_changed = 97 [(module) = "settings"];
+        ConnectivityStateChanged connectivity_state_changed = 98 [(module) = "framework"];
         // TODO: service state change is very noisy shortly after boot, as well
         // as at other transitions - coming out of doze, device plugged in, etc.
         // Consider removing this if it becomes a problem
-        ServiceStateChanged service_state_changed = 99;
-        ServiceLaunchReported service_launch_reported = 100;
-        FlagFlipUpdateOccurred flag_flip_update_occurred = 101;
-        BinaryPushStateChanged binary_push_state_changed = 102;
-        DevicePolicyEvent device_policy_event = 103;
-        DocsUIFileOperationCanceledReported docs_ui_file_op_canceled =
-            104 [(log_from_module) = "docsui"];
-        DocsUIFileOperationCopyMoveModeReported
-            docs_ui_file_op_copy_move_mode_reported =
-            105 [(log_from_module) = "docsui"];
-        DocsUIFileOperationFailureReported docs_ui_file_op_failure =
-            106 [(log_from_module) = "docsui"];
-        DocsUIFileOperationReported docs_ui_provider_file_op =
-            107 [(log_from_module) = "docsui"];
-        DocsUIInvalidScopedAccessRequestReported
-            docs_ui_invalid_scoped_access_request =
-            108 [(log_from_module) = "docsui"];
-        DocsUILaunchReported docs_ui_launch_reported =
-            109 [(log_from_module) = "docsui"];
-        DocsUIRootVisitedReported docs_ui_root_visited =
-            110 [(log_from_module) = "docsui"];
-        DocsUIStartupMsReported docs_ui_startup_ms =
-            111 [(log_from_module) = "docsui"];
-        DocsUIUserActionReported docs_ui_user_action_reported =
-            112 [(log_from_module) = "docsui"];
-        WifiEnabledStateChanged wifi_enabled_state_changed = 113;
-        WifiRunningStateChanged wifi_running_state_changed = 114;
-        AppCompacted app_compacted = 115;
-        NetworkDnsEventReported network_dns_event_reported = 116 [(log_from_module) = "resolv"];
+        ServiceStateChanged service_state_changed = 99 [(module) = "framework"];
+        ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"];
+        FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"];
+        BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"];
+        DevicePolicyEvent device_policy_event = 103 [(module) = "framework"];
+        DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"];
+        DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported =
+            105 [(module) = "docsui"];
+        DocsUIFileOperationFailureReported docs_ui_file_op_failure = 106 [(module) = "docsui"];
+        DocsUIFileOperationReported docs_ui_provider_file_op = 107 [(module) = "docsui"];
+        DocsUIInvalidScopedAccessRequestReported docs_ui_invalid_scoped_access_request =
+            108 [(module) = "docsui"];
+        DocsUILaunchReported docs_ui_launch_reported = 109 [(module) = "docsui"];
+        DocsUIRootVisitedReported docs_ui_root_visited = 110 [(module) = "docsui"];
+        DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"];
+        DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"];
+        WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"];
+        WifiRunningStateChanged wifi_running_state_changed = 114
+                [(module) = "framework", deprecated = true];
+        AppCompacted app_compacted = 115 [(module) = "framework"];
+        NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"];
         DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported =
-            117 [(log_from_module) = "docsui"];
-        DocsUIPickResultReported docs_ui_pick_result_reported =
-            118 [(log_from_module) = "docsui"];
-        DocsUISearchModeReported docs_ui_search_mode_reported =
-            119 [(log_from_module) = "docsui"];
-        DocsUISearchTypeReported docs_ui_search_type_reported =
-            120 [(log_from_module) = "docsui"];
-        DataStallEvent data_stall_event = 121 [(log_from_module) = "network_stack"];
-        RescuePartyResetReported rescue_party_reset_reported = 122;
-        SignedConfigReported signed_config_reported = 123;
-        GnssNiEventReported gnss_ni_event_reported = 124;
+            117 [(module) = "docsui"];
+        DocsUIPickResultReported docs_ui_pick_result_reported = 118 [(module) = "docsui"];
+        DocsUISearchModeReported docs_ui_search_mode_reported = 119 [(module) = "docsui"];
+        DocsUISearchTypeReported docs_ui_search_type_reported = 120 [(module) = "docsui"];
+        DataStallEvent data_stall_event = 121 [(module) = "network_stack"];
+        RescuePartyResetReported rescue_party_reset_reported = 122 [(module) = "framework"];
+        SignedConfigReported signed_config_reported = 123 [(module) = "framework"];
+        GnssNiEventReported gnss_ni_event_reported = 124 [(module) = "framework"];
         BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event =
-                125 [(log_from_module) = "bluetooth"];
+                125 [(module) = "bluetooth"];
         BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed =
-                126 [(log_from_module) = "bluetooth"];
+                126 [(module) = "bluetooth"];
         BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed =
-                127 [(log_from_module) = "bluetooth"];
-        AppDowngraded app_downgraded = 128;
+                127 [(module) = "bluetooth"];
+        AppDowngraded app_downgraded = 128 [(module) = "framework"];
         AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129;
-        LowStorageStateChanged low_storage_state_changed = 130;
-        GnssNfwNotificationReported gnss_nfw_notification_reported = 131;
-        GnssConfigurationReported gnss_configuration_reported = 132;
+        LowStorageStateChanged low_storage_state_changed = 130 [(module) = "framework"];
+        GnssNfwNotificationReported gnss_nfw_notification_reported = 131 [(module) = "framework"];
+        GnssConfigurationReported gnss_configuration_reported = 132 [(module) = "framework"];
         UsbPortOverheatEvent usb_port_overheat_event_reported = 133;
-        NfcErrorOccurred nfc_error_occurred = 134;
-        NfcStateChanged nfc_state_changed = 135;
-        NfcBeamOccurred nfc_beam_occurred = 136;
-        NfcCardemulationOccurred nfc_cardemulation_occurred = 137;
-        NfcTagOccurred nfc_tag_occurred = 138;
-        NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139;
-        SeStateChanged se_state_changed = 140;
-        SeOmapiReported se_omapi_reported = 141;
-        BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
-        AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
-        AdbConnectionChanged adb_connection_changed = 144;
+        NfcErrorOccurred nfc_error_occurred = 134 [(module) = "nfc"];
+        NfcStateChanged nfc_state_changed = 135 [(module) = "nfc"];
+        NfcBeamOccurred nfc_beam_occurred = 136 [(module) = "nfc"];
+        NfcCardemulationOccurred nfc_cardemulation_occurred = 137 [(module) = "nfc"];
+        NfcTagOccurred nfc_tag_occurred = 138 [(module) = "nfc"];
+        NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139 [(module) = "nfc"];
+        SeStateChanged se_state_changed = 140 [(module) = "secure_element"];
+        SeOmapiReported se_omapi_reported = 141 [(module) = "secure_element"];
+        BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported =
+                142 [(module) = "framework"];
+        AttentionManagerServiceResultReported attention_manager_service_result_reported =
+                143 [(module) = "framework"];
+        AdbConnectionChanged adb_connection_changed = 144 [(module) = "framework"];
         SpeechDspStatReported speech_dsp_stat_reported = 145;
-        UsbContaminantReported usb_contaminant_reported = 146;
-        WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
-        BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148;
-        BubbleUIChanged bubble_ui_changed = 149;
-        ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150;
+        UsbContaminantReported usb_contaminant_reported = 146 [(module) = "framework"];
+        WatchdogRollbackOccurred watchdog_rollback_occurred =
+                147 [(module) = "framework", (module) = "statsd"];
+        BiometricSystemHealthIssueDetected biometric_system_health_issue_detected =
+                148 [(module) = "framework"];
+        BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"];
+        ScheduledJobConstraintChanged scheduled_job_constraint_changed =
+                150 [(module) = "framework"];
         BluetoothActiveDeviceChanged bluetooth_active_device_changed =
-                151 [(log_from_module) = "bluetooth"];
+                151 [(module) = "bluetooth"];
         BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed =
-                152 [(log_from_module) = "bluetooth"];
+                152 [(module) = "bluetooth"];
         BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed =
-                153 [(log_from_module) = "bluetooth"];
+                153 [(module) = "bluetooth"];
         BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed =
-                154 [(log_from_module) = "bluetooth"];
+                154 [(module) = "bluetooth"];
         BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported =
-                155 [(log_from_module) = "bluetooth"];
+                155 [(module) = "bluetooth"];
         BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported =
-                156 [(log_from_module) = "bluetooth"];
+                156 [(module) = "bluetooth"];
         BluetoothDeviceRssiReported bluetooth_device_rssi_reported =
-                157 [(log_from_module) = "bluetooth"];
+                157 [(module) = "bluetooth"];
         BluetoothDeviceFailedContactCounterReported
-                bluetooth_device_failed_contact_counter_reported = 158 [(log_from_module) = "bluetooth"];
+                bluetooth_device_failed_contact_counter_reported = 158 [(module) = "bluetooth"];
         BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported =
-                159 [(log_from_module) = "bluetooth"];
+                159 [(module) = "bluetooth"];
         BluetoothHciTimeoutReported bluetooth_hci_timeout_reported =
-                160 [(log_from_module) = "bluetooth"];
+                160 [(module) = "bluetooth"];
         BluetoothQualityReportReported bluetooth_quality_report_reported =
-                161 [(log_from_module) = "bluetooth"];
+                161 [(module) = "bluetooth"];
         BluetoothDeviceInfoReported bluetooth_device_info_reported =
-                162 [(log_from_module) = "bluetooth"];
+                162 [(module) = "bluetooth"];
         BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported =
-                163 [(log_from_module) = "bluetooth"];
+                163 [(module) = "bluetooth"];
         BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported =
-                164 [(log_from_module) = "bluetooth"];
+                164 [(module) = "bluetooth"];
         BluetoothBondStateChanged bluetooth_bond_state_changed =
-                165 [(log_from_module) = "bluetooth"];
+                165 [(module) = "bluetooth"];
         BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported =
-                166 [(log_from_module) = "bluetooth"];
+                166 [(module) = "bluetooth"];
         BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported =
-                167 [(log_from_module) = "bluetooth"];
-        ScreenTimeoutExtensionReported screen_timeout_extension_reported = 168;
-        ProcessStartTime process_start_time = 169;
+                167 [(module) = "bluetooth"];
+        ScreenTimeoutExtensionReported screen_timeout_extension_reported =
+                168 [(module) = "framework"];
+        ProcessStartTime process_start_time = 169 [(module) = "framework"];
         PermissionGrantRequestResultReported permission_grant_request_result_reported =
-            170 [(log_from_module) = "permissioncontroller"];
+                170 [(module) = "permissioncontroller"];
         BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171;
-        DeviceIdentifierAccessDenied device_identifier_access_denied = 172;
-        BubbleDeveloperErrorReported bubble_developer_error_reported = 173;
-        AssistGestureStageReported assist_gesture_stage_reported = 174;
-        AssistGestureFeedbackReported assist_gesture_feedback_reported = 175;
-        AssistGestureProgressReported assist_gesture_progress_reported = 176;
-        TouchGestureClassified touch_gesture_classified = 177;
-        HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true];
-        StyleUIChanged style_ui_changed = 179 [(log_from_module) = "style"];
+        DeviceIdentifierAccessDenied device_identifier_access_denied =
+                172 [(module) = "telephony_common"];
+        BubbleDeveloperErrorReported bubble_developer_error_reported = 173 [(module) = "framework"];
+        AssistGestureStageReported assist_gesture_stage_reported = 174 [(module) = "sysui"];
+        AssistGestureFeedbackReported assist_gesture_feedback_reported = 175 [(module) = "sysui"];
+        AssistGestureProgressReported assist_gesture_progress_reported = 176 [(module) = "sysui"];
+        TouchGestureClassified touch_gesture_classified = 177 [(module) = "framework"];
+        HiddenApiUsed hidden_api_used = 178 [(module) = "framework"];
+        StyleUIChanged style_ui_changed = 179 [(module) = "sysui"];
         PrivacyIndicatorsInteracted privacy_indicators_interacted =
-            180 [(log_from_module) = "permissioncontroller"];
-        AppInstallOnExternalStorageReported app_install_on_external_storage_reported = 181;
-        NetworkStackReported network_stack_reported = 182 [(log_from_module) = "network_stack"];
-        AppMovedStorageReported app_moved_storage_reported = 183;
-        BiometricEnrolled biometric_enrolled = 184;
-        SystemServerWatchdogOccurred system_server_watchdog_occurred = 185;
-        TombStoneOccurred tomb_stone_occurred = 186;
+                180 [(module) = "permissioncontroller"];
+        AppInstallOnExternalStorageReported app_install_on_external_storage_reported =
+                181 [(module) = "framework"];
+        NetworkStackReported network_stack_reported = 182 [(module) = "network_stack"];
+        AppMovedStorageReported app_moved_storage_reported = 183 [(module) = "framework"];
+        BiometricEnrolled biometric_enrolled = 184 [(module) = "framework"];
+        SystemServerWatchdogOccurred system_server_watchdog_occurred = 185 [(module) = "framework"];
+        TombStoneOccurred tomb_stone_occurred = 186 [(module) = "framework"];
         BluetoothClassOfDeviceReported bluetooth_class_of_device_reported =
-                187 [(log_from_module) = "bluetooth"];
+                187 [(module) = "bluetooth"];
         IntelligenceEventReported intelligence_event_reported =
-            188 [(log_from_module) = "intelligence"];
-        ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = 189;
+                188 [(module) = "intelligence"];
+        ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed =
+                189 [(module) = "framework"];
         RoleRequestResultReported role_request_result_reported =
-            190 [(log_from_module) = "permissioncontroller"];
+                190 [(module) = "permissioncontroller"];
         MediametricsAudiopolicyReported mediametrics_audiopolicy_reported = 191;
         MediametricsAudiorecordReported mediametrics_audiorecord_reported = 192;
         MediametricsAudiothreadReported mediametrics_audiothread_reported = 193;
@@ -321,131 +344,258 @@
         MediametricsMediadrmReported mediametrics_mediadrm_reported = 198;
         MediametricsNuPlayerReported mediametrics_nuplayer_reported = 199;
         MediametricsRecorderReported mediametrics_recorder_reported = 200;
-        CarPowerStateChanged car_power_state_changed = 203;
-        GarageModeInfo garage_mode_info = 204;
-        TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
-        ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported = 206;
-        ContentCaptureServiceEvents content_capture_service_events = 207;
-        ContentCaptureSessionEvents content_capture_session_events = 208;
-        ContentCaptureFlushed content_capture_flushed = 209;
-        LocationManagerApiUsageReported location_manager_api_usage_reported = 210;
+        MediametricsDrmManagerReported mediametrics_drmmanager_reported = 201;
+        CarPowerStateChanged car_power_state_changed = 203 [(module) = "car"];
+        GarageModeInfo garage_mode_info = 204 [(module) = "car"];
+        TestAtomReported test_atom_reported = 205 [(module) = "cts"];
+        ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported =
+                206 [(module) = "framework"];
+        ContentCaptureServiceEvents content_capture_service_events = 207 [(module) = "framework"];
+        ContentCaptureSessionEvents content_capture_session_events = 208 [(module) = "framework"];
+        ContentCaptureFlushed content_capture_flushed = 209 [(module) = "framework"];
+        LocationManagerApiUsageReported location_manager_api_usage_reported =
+                210 [(module) = "framework"];
         ReviewPermissionsFragmentResultReported review_permissions_fragment_result_reported =
-            211 [(log_from_module) = "permissioncontroller"];
+                211 [(module) = "permissioncontroller"];
         RuntimePermissionsUpgradeResult runtime_permissions_upgrade_result =
-            212 [(log_from_module) = "permissioncontroller"];
+                212 [(module) = "permissioncontroller"];
         GrantPermissionsActivityButtonActions grant_permissions_activity_button_actions =
-            213 [(log_from_module) = "permissioncontroller"];
+                213 [(module) = "permissioncontroller"];
         LocationAccessCheckNotificationAction location_access_check_notification_action =
-            214 [(log_from_module) = "permissioncontroller"];
+                214 [(module) = "permissioncontroller"];
         AppPermissionFragmentActionReported app_permission_fragment_action_reported =
-            215 [(log_from_module) = "permissioncontroller"];
+                215 [(module) = "permissioncontroller"];
         AppPermissionFragmentViewed app_permission_fragment_viewed =
-            216 [(log_from_module) = "permissioncontroller"];
+                216 [(module) = "permissioncontroller"];
         AppPermissionsFragmentViewed app_permissions_fragment_viewed =
-            217 [(log_from_module) = "permissioncontroller"];
+                217 [(module) = "permissioncontroller"];
         PermissionAppsFragmentViewed permission_apps_fragment_viewed =
-            218  [(log_from_module) = "permissioncontroller"];
-        ExclusionRectStateChanged exclusion_rect_state_changed = 223;
-        BackGesture back_gesture_reported_reported = 224;
-
+                218  [(module) = "permissioncontroller"];
+        TextSelectionEvent text_selection_event = 219  [(module) = "textclassifier"];
+        TextLinkifyEvent text_linkify_event = 220  [(module) = "textclassifier"];
+        ConversationActionsEvent conversation_actions_event = 221  [(module) = "textclassifier"];
+        LanguageDetectionEvent language_detection_event = 222  [(module) = "textclassifier"];
+        ExclusionRectStateChanged exclusion_rect_state_changed = 223 [(module) = "framework"];
+        BackGesture back_gesture_reported_reported = 224 [(module) = "sysui"];
         UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225;
         UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226;
+        CameraActionEvent camera_action_event = 227 [(module) = "framework"];
         AppCompatibilityChangeReported app_compatibility_change_reported =
-            228 [(allow_from_any_uid) = true];
-        PerfettoUploaded perfetto_uploaded =
-            229 [(log_from_module) = "perfetto"];
-        VmsClientConnectionStateChanged vms_client_connection_state_changed = 230;
-        BootTimeEventDuration boot_time_event_duration_reported = 239;
-        BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
+                228 [(module) = "framework"];
+        PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
+        VmsClientConnectionStateChanged vms_client_connection_state_changed =
+                230 [(module) = "car"];
+        MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"];
+        MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"];
+        MediaProviderPermissionRequested media_provider_permission_requested =
+            235 [(module) = "mediaprovider"];
+        MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"];
+        MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished =
+            237 [(module) = "mediaprovider"];
+        RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"];
+        BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"];
+        BootTimeEventElapsedTime boot_time_event_elapsed_time_reported =
+                240 [(module) = "framework"];
         BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
-        BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
-        UserspaceRebootReported userspace_reboot_reported = 243 [(log_from_module) = "framework"];
+        BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
+        UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
+        NotificationReported notification_reported = 244 [(module) = "framework"];
+        NotificationPanelReported notification_panel_reported = 245 [(module) = "sysui"];
+        NotificationChannelModified notification_channel_modified = 246 [(module) = "framework"];
+        IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"];
+        IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"];
+        CellBroadcastMessageReported cb_message_reported =
+            249 [(module) = "cellbroadcast"];
+        CellBroadcastMessageError cb_message_error =
+            250 [(module) = "cellbroadcast"];
+        WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"];
+        WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"];
+        WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
+        AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"];
         SnapshotMergeReported snapshot_merge_reported = 255;
-        NetworkIpProvisioningReported network_ip_provisioning_reported = 290 [(log_from_module) = "network_stack"];
-        NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(log_from_module) = "network_stack"];
-        NetworkValidationReported network_validation_reported = 292 [(log_from_module) = "network_stack"];
-        NetworkStackQuirkReported network_stack_quirk_reported = 293 [(log_from_module) = "network_stack"];
+        ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended =
+            256  [(module) = "framework"];
+        DisplayJankReported display_jank_reported = 257;
+        AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
+        SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
+        RankingSelected ranking_selected = 260 [(module) = "framework", (module) = "sysui"];
+        TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"];
+        LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"];
+        PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"];
+        UserLifecycleJourneyReported user_lifecycle_journey_reported = 264 [(module) = "framework"];
+        UserLifecycleEventOccurred user_lifecycle_event_occurred = 265 [(module) = "framework"];
+        AccessibilityShortcutReported accessibility_shortcut_reported =
+            266 [(module) = "framework"];
+        AccessibilityServiceReported accessibility_service_reported = 267 [(module) = "settings"];
+        DocsUIDragAndDropReported docs_ui_drag_and_drop_reported = 268 [(module) = "docsui"];
+        AppUsageEventOccurred app_usage_event_occurred = 269 [(module) = "framework"];
+        AutoRevokeNotificationClicked auto_revoke_notification_clicked =
+            270 [(module) = "permissioncontroller"];
+        AutoRevokeFragmentAppViewed auto_revoke_fragment_app_viewed =
+            271 [(module) = "permissioncontroller"];
+        AutoRevokedAppInteraction auto_revoked_app_interaction =
+            272 [(module) = "permissioncontroller", (module) = "settings"];
+        AppPermissionGroupsFragmentAutoRevokeAction
+            app_permission_groups_fragment_auto_revoke_action =
+            273 [(module) = "permissioncontroller"];
+        EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"];
+        AudioPowerUsageDataReported audio_power_usage_data_reported = 275;
+        TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"];
+        MediaOutputOpSwitchReported mediaoutput_op_switch_reported =
+            277 [(module) = "settings"];
+        CellBroadcastMessageFiltered cb_message_filtered =
+            278 [(module) = "cellbroadcast"];
+        TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"];
+        TvCasSessionOpenStatus tv_cas_session_open_status =
+            280 [(module) = "framework"];
+        AssistantInvocationReported assistant_invocation_reported = 281 [(module) = "framework"];
+        DisplayWakeReported display_wake_reported = 282 [(module) = "framework"];
+        CarUserHalModifyUserRequestReported car_user_hal_modify_user_request_reported =
+            283 [(module) = "car"];
+        CarUserHalModifyUserResponseReported car_user_hal_modify_user_response_reported =
+            284 [(module) = "car"];
+        CarUserHalPostSwitchResponseReported car_user_hal_post_switch_response_reported =
+            285 [(module) = "car"];
+        CarUserHalInitialUserInfoRequestReported car_user_hal_initial_user_info_request_reported =
+            286 [(module) = "car"];
+        CarUserHalInitialUserInfoResponseReported car_user_hal_initial_user_info_response_reported =
+            287 [(module) = "car"];
+        CarUserHalUserAssociationRequestReported car_user_hal_user_association_request_reported =
+            288 [(module) = "car"];
+        CarUserHalSetUserAssociationResponseReported car_user_hal_set_user_association_response_reported =
+            289 [(module) = "car"];
+        NetworkIpProvisioningReported network_ip_provisioning_reported =
+            290 [(module) = "network_stack"];
+        NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(module) = "network_stack"];
+        NetworkValidationReported network_validation_reported = 292 [(module) = "network_stack"];
+        NetworkStackQuirkReported network_stack_quirk_reported = 293 [(module) = "network_stack"];
+        MediametricsAudioRecordDeviceUsageReported mediametrics_audiorecorddeviceusage_reported =
+            294;
+        MediametricsAudioThreadDeviceUsageReported mediametrics_audiothreaddeviceusage_reported =
+            295;
+        MediametricsAudioTrackDeviceUsageReported mediametrics_audiotrackdeviceusage_reported =
+            296;
+        MediametricsAudioDeviceConnectionReported mediametrics_audiodeviceconnection_reported =
+            297;
+        BlobCommitted blob_committed = 298 [(module) = "framework"];
+        BlobLeased blob_leased = 299 [(module) = "framework"];
+        BlobOpened blob_opened = 300 [(module) = "framework"];
+        ContactsProviderStatusReported contacts_provider_status_reported = 301;
         KeystoreKeyEventReported keystore_key_event_reported = 302;
-        NetworkTetheringReported  network_tethering_reported = 303 [(log_from_module) =  "network_tethering"];
+        NetworkTetheringReported  network_tethering_reported =
+            303 [(module) = "network_tethering"];
+        ImeTouchReported ime_touch_reported = 304 [(module) = "sysui"];
+
+        // StatsdStats tracks platform atoms with ids upto 500.
+        // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10080
+    // Next: 10084
     oneof pulled {
-        WifiBytesTransfer wifi_bytes_transfer = 10000;
-        WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
-        MobileBytesTransfer mobile_bytes_transfer = 10002;
-        MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 10003;
-        BluetoothBytesTransfer bluetooth_bytes_transfer = 10006;
-        KernelWakelock kernel_wakelock = 10004;
-        SubsystemSleepState subsystem_sleep_state = 10005;
-        CpuTimePerFreq cpu_time_per_freq = 10008;
-        CpuTimePerUid cpu_time_per_uid = 10009;
-        CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
-        WifiActivityInfo wifi_activity_info = 10011;
-        ModemActivityInfo modem_activity_info = 10012;
-        BluetoothActivityInfo bluetooth_activity_info = 10007;
-        ProcessMemoryState process_memory_state = 10013;
-        SystemElapsedRealtime system_elapsed_realtime = 10014;
-        SystemUptime system_uptime = 10015;
-        CpuActiveTime cpu_active_time = 10016;
-        CpuClusterTime cpu_cluster_time = 10017;
-        DiskSpace disk_space = 10018 [deprecated=true];
-        RemainingBatteryCapacity remaining_battery_capacity = 10019;
-        FullBatteryCapacity full_battery_capacity = 10020;
-        Temperature temperature = 10021;
-        BinderCalls binder_calls = 10022;
-        BinderCallsExceptions binder_calls_exceptions = 10023;
-        LooperStats looper_stats = 10024;
-        DiskStats disk_stats = 10025;
-        DirectoryUsage directory_usage = 10026;
-        AppSize app_size = 10027;
-        CategorySize category_size = 10028;
-        ProcStats proc_stats = 10029;
-        BatteryVoltage battery_voltage = 10030;
-        NumFingerprintsEnrolled num_fingerprints_enrolled = 10031;
-        DiskIo disk_io = 10032;
-        PowerProfile power_profile = 10033;
-        ProcStatsPkgProc proc_stats_pkg_proc = 10034;
-        ProcessCpuTime process_cpu_time = 10035;
-        NativeProcessMemoryState native_process_memory_state = 10036;
-        CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
+        WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"];
+        WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"];
+        MobileBytesTransfer mobile_bytes_transfer =
+                10002 [(module) = "framework", (truncate_timestamp) = true];
+        MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg =
+                10003 [(module) = "framework", (truncate_timestamp) = true];
+        BluetoothBytesTransfer bluetooth_bytes_transfer = 10006 [(module) = "framework"];
+        KernelWakelock kernel_wakelock = 10004 [(module) = "framework"];
+        SubsystemSleepState subsystem_sleep_state = 10005 [(module) = "statsdtest"];
+        CpuTimePerFreq cpu_time_per_freq = 10008 [(module) = "framework"];
+        CpuTimePerUid cpu_time_per_uid = 10009 [(module) = "framework", (module) = "statsdtest"];
+        CpuTimePerUidFreq cpu_time_per_uid_freq =
+                10010 [(module) = "framework", (module) = "statsd"];
+        WifiActivityInfo wifi_activity_info = 10011 [(module) = "framework"];
+        ModemActivityInfo modem_activity_info = 10012 [(module) = "framework"];
+        BluetoothActivityInfo bluetooth_activity_info = 10007 [(module) = "framework"];
+        ProcessMemoryState process_memory_state = 10013 [(module) = "framework"];
+        SystemElapsedRealtime system_elapsed_realtime = 10014 [(module) = "framework"];
+        SystemUptime system_uptime = 10015 [(module) = "framework"];
+        CpuActiveTime cpu_active_time = 10016 [(module) = "framework", (module) = "statsdtest"];
+        CpuClusterTime cpu_cluster_time = 10017 [(module) = "framework"];
+        DiskSpace disk_space = 10018 [deprecated=true, (module) = "statsdtest"];
+        RemainingBatteryCapacity remaining_battery_capacity = 10019 [(module) = "framework"];
+        FullBatteryCapacity full_battery_capacity = 10020 [(module) = "framework"];
+        Temperature temperature = 10021 [(module) = "framework", (module) = "statsdtest"];
+        BinderCalls binder_calls = 10022 [(module) = "framework", (module) = "statsd"];
+        BinderCallsExceptions binder_calls_exceptions = 10023 [(module) = "framework"];
+        LooperStats looper_stats = 10024 [(module) = "framework", (module) = "statsd"];
+        DiskStats disk_stats = 10025 [(module) = "framework"];
+        DirectoryUsage directory_usage = 10026 [(module) = "framework"];
+        AppSize app_size = 10027 [(module) = "framework"];
+        CategorySize category_size = 10028 [(module) = "framework"];
+        ProcStats proc_stats = 10029 [(module) = "framework"];
+        BatteryVoltage battery_voltage = 10030 [(module) = "framework"];
+        NumFingerprintsEnrolled num_fingerprints_enrolled = 10031 [(module) = "framework"];
+        DiskIo disk_io = 10032 [(module) = "framework"];
+        PowerProfile power_profile = 10033 [(module) = "framework"];
+        ProcStatsPkgProc proc_stats_pkg_proc = 10034 [(module) = "framework"];
+        ProcessCpuTime process_cpu_time = 10035 [(module) = "framework"];
+        CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037 [(module) = "framework"];
         OnDevicePowerMeasurement on_device_power_measurement = 10038;
-        DeviceCalculatedPowerUse device_calculated_power_use = 10039;
-        DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = 10040;
-        DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = 10041;
-        ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042;
-        BatteryLevel battery_level = 10043;
-        BuildInformation build_information = 10044;
-        BatteryCycleCount battery_cycle_count = 10045;
-        DebugElapsedClock debug_elapsed_clock = 10046;
-        DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
-        NumFacesEnrolled num_faces_enrolled = 10048;
-        RoleHolder role_holder = 10049;
-        DangerousPermissionState dangerous_permission_state = 10050;
-        TrainInfo train_info = 10051;
-        TimeZoneDataInfo time_zone_data_info = 10052;
-        ExternalStorageInfo external_storage_info = 10053;
+        DeviceCalculatedPowerUse device_calculated_power_use = 10039 [(module) = "framework"];
+        DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid =
+                10040 [(module) = "framework"];
+        DeviceCalculatedPowerBlameOther device_calculated_power_blame_other =
+                10041 [(module) = "framework"];
+        ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042 [(module) = "framework"];
+        BatteryLevel battery_level = 10043 [(module) = "framework"];
+        BuildInformation build_information = 10044 [(module) = "framework"];
+        BatteryCycleCount battery_cycle_count = 10045 [(module) = "framework"];
+        DebugElapsedClock debug_elapsed_clock = 10046 [(module) = "framework"];
+        DebugFailingElapsedClock debug_failing_elapsed_clock = 10047 [(module) = "framework"];
+        NumFacesEnrolled num_faces_enrolled = 10048 [(module) = "framework"];
+        RoleHolder role_holder = 10049 [(module) = "framework"];
+        DangerousPermissionState dangerous_permission_state = 10050 [(module) = "framework"];
+        TrainInfo train_info = 10051 [(module) = "statsd"];
+        TimeZoneDataInfo time_zone_data_info = 10052 [(module) = "framework"];
+        ExternalStorageInfo external_storage_info = 10053 [(module) = "framework"];
         GpuStatsGlobalInfo gpu_stats_global_info = 10054;
         GpuStatsAppInfo gpu_stats_app_info = 10055;
-        SystemIonHeapSize system_ion_heap_size = 10056;
-        AppsOnExternalStorageInfo apps_on_external_storage_info = 10057;
-        FaceSettings face_settings = 10058;
-        CoolingDevice cooling_device = 10059;
-        AppOps app_ops = 10060;
-        ProcessSystemIonHeapSize process_system_ion_heap_size = 10061;
-        VmsClientStats vms_client_stats = 10065;
-        NotificationRemoteViews notification_remote_views = 10066;
-        VoiceCallSession voice_call_session = 10076 [(log_from_module) = "telephony"];
-        VoiceCallRatUsage voice_call_rat_usage = 10077 [(log_from_module) = "telephony"];
-        SimSlotState sim_slot_state = 10078 [(log_from_module) = "telephony"];
-        SupportedRadioAccessFamily supported_radio_access_family =
-            10079 [(log_from_module) = "telephony"];
+        SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true, (module) = "framework"];
+        AppsOnExternalStorageInfo apps_on_external_storage_info = 10057 [(module) = "framework"];
+        FaceSettings face_settings = 10058 [(module) = "framework"];
+        CoolingDevice cooling_device = 10059 [(module) = "framework"];
+        AppOps app_ops = 10060 [(module) = "framework"];
+        ProcessSystemIonHeapSize process_system_ion_heap_size = 10061 [(module) = "framework"];
+        SurfaceflingerStatsGlobalInfo surfaceflinger_stats_global_info = 10062;
+        SurfaceflingerStatsLayerInfo surfaceflinger_stats_layer_info = 10063;
+        ProcessMemorySnapshot process_memory_snapshot = 10064 [(module) = "framework"];
+        VmsClientStats vms_client_stats = 10065 [(module) = "car"];
+        NotificationRemoteViews notification_remote_views = 10066 [(module) = "framework"];
+        DangerousPermissionStateSampled dangerous_permission_state_sampled =
+                10067 [(module) = "framework"];
+        GraphicsStats graphics_stats = 10068;
+        RuntimeAppOpAccess runtime_app_op_access = 10069 [(module) = "framework"];
+        IonHeapSize ion_heap_size = 10070 [(module) = "framework"];
+        PackageNotificationPreferences package_notification_preferences =
+                10071 [(module) = "framework"];
+        PackageNotificationChannelPreferences package_notification_channel_preferences =
+                10072 [(module) = "framework"];
+        PackageNotificationChannelGroupPreferences package_notification_channel_group_preferences =
+                10073 [(module) = "framework"];
+        GnssStats gnss_stats = 10074 [(module) = "framework"];
+        AttributedAppOps attributed_app_ops = 10075 [(module) = "framework"];
+        VoiceCallSession voice_call_session = 10076 [(module) = "telephony"];
+        VoiceCallRatUsage voice_call_rat_usage = 10077 [(module) = "telephony"];
+        SimSlotState sim_slot_state = 10078 [(module) = "telephony"];
+        SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"];
+        SettingSnapshot setting_snapshot = 10080 [(module) = "framework"];
+        BlobInfo blob_info = 10081 [(module) = "framework"];
+        DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"];
+        BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
+                10083 [(module) = "framework"];
+        DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"];
+        GeneralExternalStorageAccessStats general_external_storage_access_stats =
+            10085 [(module) = "mediaprovider"];
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
     // Field numbers 100,000 - 199,999 are reserved for non-AOSP (e.g. OEMs) to use.
     // Field numbers 200,000 and above are reserved for future use; do not use them at all.
+
+    reserved 10036;
 }
 
 /**
@@ -534,7 +684,8 @@
  */
 message ScreenStateChanged {
     // New screen state, from frameworks/base/core/proto/android/view/enums.proto.
-    optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE];
+    optional android.view.DisplayStateEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -545,10 +696,11 @@
  *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
  */
 message UidProcessStateChanged {
-    optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true];
+    optional int32 uid = 1 [(state_field_option).primary_field = true, (is_uid) = true];
 
     // The state, from frameworks/base/core/proto/android/app/enums.proto.
-    optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE];
+    optional android.app.ProcessStateEnum state = 2
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -580,7 +732,8 @@
         ASLEEP = 1;
         AWAKE = 2;
     }
-    optional State state = 1 [(state_field_option).option = EXCLUSIVE];
+    optional State state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -599,7 +752,7 @@
         CRITICAL = 4;   // critical memory.
 
     }
-    optional State factor = 1 [(state_field_option).option = EXCLUSIVE];
+    optional State factor = 1 [(state_field_option).exclusive_state = true];
 }
 
 /**
@@ -632,6 +785,96 @@
 }
 
 /**
+ * Logs the change in wifi health.
+ *
+ * Logged from:
+ *   frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiDataStall.java
+ */
+message WifiHealthStatReported {
+    enum Band {
+        UNKNOWN = 0;
+        // All of 2.4GHz band
+        BAND_2G = 1;
+        // Frequencies in the range of [5150, 5250) GHz
+        BAND_5G_LOW = 2;
+        // Frequencies in the range of [5250, 5725) GHz
+        BAND_5G_MIDDLE = 3;
+        // Frequencies in the range of [5725, 5850) GHz
+        BAND_5G_HIGH = 4;
+        // Frequencies in the range of [5925, 6425) GHz
+        BAND_6G_LOW = 5;
+        // Frequencies in the range of [6425, 6875) GHz
+        BAND_6G_MIDDLE = 6;
+        // Frequencies in the range of [6875, 7125) GHz
+        BAND_6G_HIGH = 7;
+    }
+    // duration this stat is obtained over in milliseconds
+    optional int32 duration_millis = 1;
+    // whether wifi is classified as sufficient for the user's data traffic, determined
+    // by whether the calculated throughput exceeds the average demand within |duration_millis|
+    optional bool is_sufficient = 2;
+    // whether cellular data is available
+    optional bool is_cell_data_available = 3;
+    // the Band bucket the connected network is on
+    optional Band band = 4;
+}
+
+/**
+ * Logged when wifi detects a significant change in connection failure rate.
+ *
+ * Logged from: frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiHealthMonitor.java
+ *
+ */
+message WifiFailureStatReported {
+    enum AbnormalityType {
+        UNKNOWN = 0;
+        SIGNIFICANT_INCREASE = 1;
+        SIGNIFICANT_DECREASE = 2;
+        SIMPLY_HIGH = 3;
+    }
+    enum FailureType {
+        FAILURE_UNKNOWN = 0;
+        FAILURE_CONNECTION = 1;
+        FAILURE_ASSOCIATION_REJECTION = 2;
+        FAILURE_ASSOCIATION_TIMEOUT = 3;
+        FAILURE_AUTHENTICATION = 4;
+        FAILURE_NON_LOCAL_DISCONNECTION = 5;
+        FAILURE_SHORT_CONNECTION_DUE_TO_NON_LOCAL_DISCONNECTION = 6;
+    }
+    // Reason for uploading this stat
+    optional AbnormalityType abnormality_type = 1;
+    // The particular type of failure
+    optional FailureType failure_type = 2;
+    // How many times we have encountered this combination of AbnormalityType and FailureType
+    optional int32 failure_count = 3;
+}
+
+/**
+ * Logs whether a wifi connection is successful and reasons for failure if it isn't.
+ *
+ * Logged from:
+ *   frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeImpl.java
+ */
+message WifiConnectionResultReported {
+    enum FailureCode {
+        FAILURE_UNKNOWN = 0;
+        FAILURE_ASSOCIATION_TIMEOUT = 1;
+        FAILURE_ASSOCIATION_REJECTION = 2;
+        FAILURE_AUTHENTICATION_GENERAL = 3;
+        FAILURE_AUTHENTICATION_EAP = 4;
+        FAILURE_DHCP = 5;
+        FAILURE_NETWORK_DISCONNECTION = 6;
+        FAILURE_ROAM_TIMEOUT = 7;
+    }
+    // true represents a successful connection
+    optional bool connection_result = 1;
+    // reason for the connection failure
+    optional FailureCode failure_code = 2;
+    // scan rssi before the connection attempt
+    optional int32 rssi = 3;
+}
+
+/**
  * Logs when memory stats of a process is reported.
  *
  * Logged from:
@@ -686,7 +929,8 @@
  *   packages/apps/Bluetooth/src/com/android/bluetooth/gatt/AppScanStats.java
  */
 message BleScanStateChanged {
-    repeated AttributionNode attribution_node = 1;
+    repeated AttributionNode attribution_node = 1
+            [(state_field_option).primary_field_first_uid = true];
 
     enum State {
         OFF = 0;
@@ -694,14 +938,19 @@
         // RESET indicates all ble stopped. Used when it (re)starts (e.g. after it crashes).
         RESET = 2;
     }
-    optional State state = 2;
+    optional State state = 2 [
+        (state_field_option).exclusive_state = true,
+        (state_field_option).default_state_value = 0 /* State.OFF */,
+        (state_field_option).trigger_state_reset_value = 2 /* State.RESET */,
+        (state_field_option).nested = true
+    ];
 
     // Does the scan have a filter.
-    optional bool is_filtered = 3;
+    optional bool is_filtered = 3 [(state_field_option).primary_field = true];
     // Whether the scan is a CALLBACK_TYPE_FIRST_MATCH scan. Called 'background' scan internally.
-    optional bool is_first_match = 4;
+    optional bool is_first_match = 4 [(state_field_option).primary_field = true];
     // Whether the scan set to piggy-back off the results of other scans (SCAN_MODE_OPPORTUNISTIC).
-    optional bool is_opportunistic = 5;
+    optional bool is_opportunistic = 5 [(state_field_option).primary_field = true];
 }
 
 /**
@@ -833,11 +1082,23 @@
         FREQUENT = 2;
         RARE = 3;
         NEVER = 4;
+        RESTRICTED = 5;
     }
     optional Bucket standby_bucket = 5 [default = UNKNOWN];
 
     // The job id (as assigned by the app).
     optional int32 job_id = 6;
+
+    // One flag for each of the API constraints defined by Jobscheduler. Does not include implcit
+    // constraints as they are always assumed to be set.
+    optional bool has_charging_constraint = 7;
+    optional bool has_battery_not_low_constraint = 8;
+    optional bool has_storage_not_low_constraint = 9;
+    optional bool has_timing_delay_constraint = 10;
+    optional bool has_deadline_constraint = 11;
+    optional bool has_idle_constraint = 12;
+    optional bool has_connectivity_constraint = 13;
+    optional bool has_content_trigger_constraint = 14;
 }
 
 /**
@@ -919,14 +1180,15 @@
  *   TODO
  */
 message WakelockStateChanged {
-    repeated AttributionNode attribution_node = 1;
+    repeated AttributionNode attribution_node = 1
+            [(state_field_option).primary_field_first_uid = true];
 
     // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock.
     // From frameworks/base/core/proto/android/os/enums.proto.
-    optional android.os.WakeLockLevelEnum type = 2;
+    optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).primary_field = true];
 
     // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
-    optional string tag = 3;
+    optional string tag = 3 [(state_field_option).primary_field = true];
 
     enum State {
         RELEASE = 0;
@@ -934,7 +1196,11 @@
         CHANGE_RELEASE = 2;
         CHANGE_ACQUIRE = 3;
     }
-    optional State state = 4;
+    optional State state = 4 [
+        (state_field_option).exclusive_state = true,
+        (state_field_option).default_state_value = 0,
+        (state_field_option).nested = true
+    ];
 }
 
 /**
@@ -984,7 +1250,8 @@
         OFF = 0;
         ON = 1;
     }
-    optional State state = 1;
+    optional State state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -994,7 +1261,8 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message DeviceIdleModeStateChanged {
-    optional android.server.DeviceIdleModeEnum state = 1;
+    optional android.server.DeviceIdleModeEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 
@@ -1005,7 +1273,8 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message DeviceIdlingModeStateChanged {
-    optional android.server.DeviceIdleModeEnum state = 1;
+    optional android.server.DeviceIdleModeEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -1038,7 +1307,8 @@
  */
 message ChargingStateChanged {
     // State of the battery, from frameworks/base/core/proto/android/os/enums.proto.
-    optional android.os.BatteryStatusEnum state = 1;
+    optional android.os.BatteryStatusEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -1049,7 +1319,8 @@
  */
 message PluggedStateChanged {
     // Whether the device is plugged in, from frameworks/base/core/proto/android/os/enums.proto.
-    optional android.os.BatteryPluggedStateEnum state = 1;
+    optional android.os.BatteryPluggedStateEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
@@ -1067,18 +1338,8 @@
     // Name of source package (for historical reasons, since BatteryStats tracked it).
     optional string package_name = 3;
 
-    // These enum values match the STANDBY_BUCKET_XXX constants defined in UsageStatsManager.java.
-    enum Bucket {
-        UNKNOWN = 0;
-        EXEMPTED = 5;
-        ACTIVE = 10;
-        WORKING_SET = 20;
-        FREQUENT = 30;
-        RARE = 40;
-        NEVER = 50;
-    }
     // The App Standby bucket of the app that scheduled the alarm at the time the alarm fired.
-    optional Bucket app_standby_bucket = 4;
+    optional AppStandbyBucketChanged.Bucket app_standby_bucket = 4;
 }
 
 /**
@@ -1139,6 +1400,8 @@
 }
 
 /**
+ * This atom is deprecated starting in R.
+ *
  * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode.
  * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'.
  * Note that Wifi Scanning is monitored separately in WifiScanStateChanged.
@@ -1789,12 +2052,15 @@
         REASON_EXPLICIT_HEALTH_CHECK = 2;
         REASON_APP_CRASH = 3;
         REASON_APP_NOT_RESPONDING = 4;
+        REASON_NATIVE_CRASH_DURING_BOOT = 5;
     }
     optional RollbackReasonType rollback_reason = 4;
 
     // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback
     // is initiated. Empty if the package is unknown.
     optional string failing_package_name = 5;
+
+    optional TrainExperimentIds experiment_ids = 6 [(log_mode) = MODE_BYTES];
 }
 
 /**
@@ -2518,8 +2784,9 @@
         STATE_DISCONNECTED = 0;
         STATE_CONNECTED = 1;
     }
-    optional State state = 1;
-    optional string id = 2;
+    optional State state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
+    optional string id = 2 [(state_field_option).primary_field = true];
     // Last active session in ms.
     // 0 when the port is in connected state.
     optional int64 last_connect_duration_millis = 3;
@@ -2750,21 +3017,32 @@
 
 message BackGesture {
     enum BackType {
-          DEFAULT_BACK_TYPE = 0;
-          COMPLETED = 1;
-          COMPLETED_REJECTED = 2; // successful because coming from rejected area
-          INCOMPLETE_EXCLUDED = 3; // would have been successful but in the exclusion area
-          INCOMPLETE = 4;
+        DEFAULT_BACK_TYPE = 0;
+        COMPLETED = 1;
+        COMPLETED_REJECTED = 2; // successful because coming from rejected area
+        INCOMPLETE_EXCLUDED = 3; // would have been successful but in the exclusion area
+        INCOMPLETE = 4;  // Unsuccessful, for reasons other than below.
+        INCOMPLETE_FAR_FROM_EDGE = 5;  // Unsuccessful, far from the edge.
+        INCOMPLETE_MULTI_TOUCH = 6;  // Unsuccessful, multi touch.
+        INCOMPLETE_LONG_PRESS = 7;  // Unsuccessful, long press.
+        INCOMPLETE_VERTICAL_MOVE = 8;  // Unsuccessful, move vertically.
     }
     optional BackType type = 1;
 
-    optional int32 y_coordinate = 2; // y coordinate for ACTION_DOWN event
+    optional int32 y_coordinate = 2 [deprecated = true]; // y coordinate for ACTION_DOWN event
+    optional int32 start_x = 4;  // X coordinate for ACTION_DOWN event.
+    optional int32 start_y = 5;  // Y coordinate for ACTION_DOWN event.
+    optional int32 end_x = 6;   // X coordinate for ACTION_MOVE event.
+    optional int32 end_y = 7;  // Y coordinate for ACTION_MOVE event.
+    optional int32 left_boundary = 8;  // left edge width + left inset
+    optional int32 right_boundary = 9;  // screen width - (right edge width + right inset)
+
     enum WindowHorizontalLocation {
         DEFAULT_LOCATION = 0;
         LEFT = 1;
         RIGHT = 2;
     }
-    optional WindowHorizontalLocation x_location = 3;
+    optional WindowHorizontalLocation x_location = 3 [deprecated = true];
 }
 
 message ExclusionRectStateChanged {
@@ -2783,14 +3061,125 @@
     optional int32 duration_millis = 7;
 }
 
-message LauncherUIChanged {
-    optional android.stats.launcher.LauncherAction action = 1;
-    optional android.stats.launcher.LauncherState src_state = 2;
-    optional android.stats.launcher.LauncherState dst_state = 3;
-    optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES];
-    optional bool is_swipe_up_enabled = 5;
+/**
+ * Logs when IME is on.
+ *
+ * Logged from: /packages/SystemUI/src/com/android/systemui/
+                statusbar/phone/NavigationBarView.java
+ *
+ */
+message ImeTouchReported {
+    optional int32 x_coordinate = 1;  // X coordinate for ACTION_DOWN event.
+    optional int32 y_coordinate = 2;  // Y coordinate for ACTION_DOWN event.
 }
 
+/**
+ * Logs when Launcher (HomeScreen) UI has changed or was interacted.
+ *
+ * Logged from:
+ *   packages/apps/Launcher3
+ */
+message LauncherUIChanged {
+    optional android.stats.launcher.LauncherAction action = 1 [deprecated = true];
+    optional android.stats.launcher.LauncherState src_state = 2;
+    optional android.stats.launcher.LauncherState dst_state = 3;
+    optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES, deprecated = true];
+    optional bool is_swipe_up_enabled = 5 [deprecated = true];
+
+    // The event id (e.g., app launch, drag and drop, long press)
+    optional int32 event_id = 6;
+    // The event's source or target id (e.g., icon, task, button)
+    optional int32 target_id = 7;
+    // If the target needs to be tracked, use this id field
+    optional int32 instance_id = 8;
+    optional int32 uid = 9 [(is_uid) = true];
+    optional string package_name = 10;
+    optional string component_name = 11;
+
+    // (x, y) coordinate and the index information of the target on the container
+    optional int32 grid_x = 12 [default = -1];
+    optional int32 grid_y = 13 [default = -1];
+    optional int32 page_id = 14 [default = -2];
+
+    // e.g., folder icon's (x, y) location and index information on the workspace
+    optional int32 grid_x_parent = 15 [default = -1];
+    optional int32 grid_y_parent = 16 [default = -1];
+    optional int32 page_id_parent = 17 [default = -2];
+
+    // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE
+    optional int32 hierarchy = 18;
+
+    optional bool is_work_profile = 19;
+
+    // Used to store the predicted rank of the target
+    optional int32 rank = 20 [default = -1];
+
+    // e.g., folderLabelState can be captured in the following two fields
+    optional int32 from_state = 21;
+    optional int32 to_state = 22;
+
+    // e.g., autofilled or suggested texts that are not user entered
+    optional string edittext = 23;
+
+    // e.g., number of contents inside a container (e.g., icons inside a folder)
+    optional int32 cardinality = 24;
+}
+
+/**
+ * Used for snapshot of the HomeScreen UI elements
+ *
+ * Logged from:
+ *   packages/apps/Launcher3
+ */
+message LauncherStaticLayout {
+    // The event id (e.g., snapshot, drag and drop)
+    optional int32 event_id = 1;
+    // The event's source or target id (e.g., icon, shortcut, widget)
+    optional int32 target_id = 2;
+    // If the target needs to be tracked, use this id field
+    optional int32 instance_id = 3;
+    optional int32 uid = 4 [(is_uid) = true];
+    optional string package_name = 5;
+    optional string component_name = 6;
+
+    // (x, y) coordinate and the index information of the target on the container
+    optional int32 grid_x = 7 [default = -1];
+    optional int32 grid_y = 8 [default = -1];
+    optional int32 page_id = 9 [default = -2];
+
+    // e.g., folder icon's (x, y) location and index information on the workspace
+    // e.g., when used with widgets target, use these values for (span_x, span_y)
+    optional int32 grid_x_parent = 10 [default = -1];
+    optional int32 grid_y_parent = 11 [default = -1];
+    optional int32 page_id_parent = 12 [default = -2];
+
+    // UNKNOWN = 0
+    // HOTSEAT = 1
+    // WORKSPACE = 2
+    // FOLDER_HOTSEAT = 3
+    // FOLDER_WORKSPACE = 4
+    optional int32 hierarchy = 13;
+
+    optional bool is_work_profile = 14;
+
+    // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION
+    optional int32 origin = 15;
+
+    // e.g., number of icons inside a folder
+    optional int32 cardinality = 16;
+
+    // e.g., (x, y) span of the widget inside homescreen grid system
+    optional int32 span_x = 17 [default = 1];
+    optional int32 span_y = 18 [default = 1];
+}
+
+/**
+ * Logs when Wallpaper or ThemePicker UI has changed.
+ *
+ * Logged from:
+ *   packages/apps/ThemePicker
+ *   packages/apps/WallpaperPicker2
+ */
 message StyleUIChanged {
     optional android.stats.style.Action action = 1;
     optional int32 color_package_hash = 2;
@@ -2848,12 +3237,14 @@
 message TouchEventReported {
     /**
      * The fields latency_{min|max|mean|stdev} represent minimum, maximum, mean,
-     * and the standard deviation of latency between the kernel and framework
-     * for touchscreen events. The units are microseconds.
+     * and the standard deviation of the time spent processing touchscreen events
+     * in the kernel and inputflinger. The units are microseconds.
      *
-     * The number is measured as the difference between the time at which
-     * the input event was received in the evdev driver,
-     * and the time at which the input event was received in EventHub.
+     * On supported devices, the starting point is taken during the hard interrupt inside the
+     * kernel touch driver. On all other devices, the starting point is taken inside
+     * the kernel's input event subsystem upon receipt of the input event.
+     * The ending point is taken inside InputDispatcher, just after the input event
+     * is sent to the app.
      */
     // Minimum value
     optional float latency_min_micros = 1;
@@ -3023,7 +3414,7 @@
     optional string process_name = 3;
 
     // The pid if available. -1 means not available.
-    optional sint32 pid = 4;
+    optional int32 pid = 4;
 
     optional string package_name = 5;
 
@@ -3059,7 +3450,7 @@
     optional string process_name = 3;
 
     // The pid if available. -1 means not available.
-    optional sint32 pid = 4;
+    optional int32 pid = 4;
 
     optional android.server.ErrorSource error_source = 5;
 }
@@ -3295,9 +3686,9 @@
  *     services/core/java/com/android/server/wm/Session.java
  */
 message OverlayStateChanged {
-    optional int32 uid = 1 [(is_uid) = true];
+    optional int32 uid = 1 [(state_field_option).primary_field = true, (is_uid) = true];
 
-    optional string package_name = 2;
+    optional string package_name = 2 [(state_field_option).primary_field = true];
 
     optional bool using_alert_window = 3;
 
@@ -3305,15 +3696,16 @@
         ENTERED = 1;
         EXITED = 2;
     }
-    optional State state = 4;
+    optional State state = 4
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
-/*
+/**
  * Logs foreground service starts and stops.
  * Note that this is not when a service starts or stops, but when it is
  * considered foreground.
  * Logged from
- *     //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
  */
 message ForegroundServiceStateChanged {
     optional int32 uid = 1 [(is_uid) = true];
@@ -3325,6 +3717,45 @@
         EXIT = 2;
     }
     optional State state = 3;
+
+    // Whether the fgs is allowed while-in-use permissions, i.e. is considered 'in-use' to the user.
+    // (If the fgs was started while the app wasn't TOP it usually will be denied these permissions)
+    optional bool allow_while_in_use_permission = 4;
+}
+
+/**
+ * Logs the number of times a uid accesses a sensitive AppOp during a foreground service session.
+ * A foreground service session is any continuous period during which the uid holds at least one
+ * foreground service; the atom will be pushed when the uid no longer holds any foreground services.
+ * Accesses initiated while the uid is in the TOP state are ignored.
+ * Sessions with no attempted accesses are not logged.
+ * Logged from
+ *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ForegroundServiceAppOpSessionEnded {
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The operation's name.
+    // Only following four ops are logged
+    // COARSE_LOCATION = 0
+    // FINE_LOCATION = 1
+    // CAMERA = 26
+    // RECORD_AUDIO = 27
+    optional android.app.AppOpEnum app_op_name = 2 [default = APP_OP_NONE];
+
+    // The uid's permission mode for accessing the AppOp during this fgs session.
+    enum Mode {
+        MODE_UNKNOWN = 0;
+        MODE_ALLOWED = 1; // Always allowed
+        MODE_IGNORED = 2; // Denied
+        MODE_FOREGROUND = 3; // Allow-while-in-use (or allowed-one-time)
+    }
+    optional Mode app_op_mode = 3;
+
+    // Number of times this AppOp was requested and allowed.
+    optional int32 count_ops_accepted = 4;
+    // Number of times this AppOp was requested but denied.
+    optional int32 count_ops_rejected = 5;
 }
 
 /**
@@ -3471,7 +3902,7 @@
  */
 message AppDied {
     // timestamp(elapsedRealtime) of record creation
-    optional uint64 timestamp_millis = 1 [(state_field_option).option = EXCLUSIVE];
+    optional uint64 timestamp_millis = 1 [(state_field_option).exclusive_state = true];
 }
 
 /**
@@ -3486,6 +3917,144 @@
 }
 
 /**
+ * Atom for simple logging of user interaction and impression events, such as "the user touched
+ * this button" or "this dialog was displayed".
+ * Keep the UI event stream clean: don't use for system or background events.
+ * Log using the UiEventLogger wrapper - don't write with the StatsLog API directly.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/
+ *   frameworks/base/packages/SystemUI/src/com/android/systemui/
+ */
+message UiEventReported {
+    // The event_id.
+    optional int32 event_id = 1;
+    // The event's source or target uid and package, if applicable.
+    // For example, the package posting a notification, or the destination package of a share.
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+    // An identifier used to disambiguate which logs refer to a particular instance of some
+    // UI element. Useful when there might be multiple instances simultaneously active.
+    optional int32 instance_id = 4;
+}
+
+/**
+ * Reports a notification was created or updated.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationReported {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The notifying app's uid and package.
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+    // A small system-assigned identifier for the notification.
+    // Locally probably-unique, but expect collisions across users and/or days.
+    optional int32 instance_id = 4;
+    optional int32 notification_id_hash = 5;  // Small hash of the app-assigned notif ID + tag
+    optional int32 channel_id_hash = 6;  // Small hash of app-assigned channel ID
+
+    // Grouping information
+    optional int32 group_id_hash = 7;  // Small hash of the group ID of the notification
+    optional int32 group_instance_id = 8;  // Instance_id of the group-summary notification
+    optional bool is_group_summary = 9;  // Tags the group-summary notification
+
+    // Attributes
+    optional string category = 10;   // App-assigned notification category (API-defined strings)
+    optional int32 style = 11;       // App-assigned notification style
+    optional int32 num_people = 12;  // Number of Person records attached to the notification
+
+    // Ordering, importance and interruptiveness
+
+    optional int32 position = 13;    // Position in NotificationManager's list
+
+    optional android.stats.sysui.NotificationImportance importance = 14;
+    optional int32 alerting = 15;    // Bitfield, 1=buzz 2=beep 4=blink
+
+    enum NotificationImportanceExplanation {
+        IMPORTANCE_EXPLANATION_UNKNOWN = 0;
+        IMPORTANCE_EXPLANATION_APP = 1;     // App-specified channel importance.
+        IMPORTANCE_EXPLANATION_USER = 2;    // User-specified channel importance.
+        IMPORTANCE_EXPLANATION_ASST = 3;    // Notification Assistant override.
+        IMPORTANCE_EXPLANATION_SYSTEM = 4;  // System override.
+        // Like _APP, but based on pre-channels priority signal.
+        IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5;
+    }
+
+    optional NotificationImportanceExplanation importance_source = 16;
+    optional android.stats.sysui.NotificationImportance importance_initial = 17;
+    optional NotificationImportanceExplanation importance_initial_source = 18;
+    optional android.stats.sysui.NotificationImportance importance_asst = 19;
+    optional int32 assistant_hash = 20;
+    optional float assistant_ranking_score = 21;
+}
+
+message Notification {
+    // The notifying app's uid and package.
+    optional int32 uid = 1 [(is_uid) = true];
+    optional string package_name = 2;
+    // A small system-assigned identifier for the notification.
+    optional int32 instance_id = 3;
+
+    // Grouping information.
+    optional int32 group_instance_id = 4;
+    optional bool is_group_summary = 5;
+
+    // The section of the shade that the notification is in.
+    // See NotificationSectionsManager.PriorityBucket.
+    enum NotificationSection {
+        SECTION_UNKNOWN = 0;
+        SECTION_HEADS_UP = 1;
+        SECTION_MEDIA_CONTROLS = 2;
+        SECTION_PEOPLE = 3;
+        SECTION_ALERTING = 4;
+        SECTION_SILENT = 5;
+    }
+    optional NotificationSection section = 6;
+}
+
+message NotificationList {
+    repeated Notification notifications = 1;  // An ordered sequence of notifications.
+}
+
+/**
+ * Reports a notification panel was displayed, e.g. from the lockscreen or status bar.
+ *
+ * Logged from:
+ *   frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
+ */
+message NotificationPanelReported {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    optional int32 num_notifications = 2;
+    // The notifications in the panel, in the order that they appear there.
+    optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES];
+}
+
+/**
+ * Reports a notification channel, or channel group, was created, updated, or deleted.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationChannelModified {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The notifying app's uid and package.
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+    // Hash of app-assigned notification channel ID or channel-group ID
+    optional int32 channel_id_hash = 4;
+    // Previous importance setting, if applicable
+    optional android.stats.sysui.NotificationImportance old_importance = 5;
+    // New importance setting
+    optional android.stats.sysui.NotificationImportance importance = 6;
+}
+
+
+/**
  * Logs when a biometric acquire event occurs.
  *
  * Logged from:
@@ -3622,6 +4191,7 @@
 
 /**
  * Potential experiment ids that goes with a train install.
+ * Should be kept in sync with experiment_ids.proto.
  */
 message TrainExperimentIds {
     repeated int64 experiment_id = 1;
@@ -3673,12 +4243,16 @@
         INSTALL_FAILURE_DOWNLOAD = 23;
         INSTALL_FAILURE_STATE_MISMATCH = 24;
         INSTALL_FAILURE_COMMIT = 25;
+        REBOOT_TRIGGERED = 26;
     }
     optional State state = 6;
     // Possible experiment ids for monitoring this push.
     optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES];
     // user id
     optional int32 user_id = 8;
+    optional int32 reason = 9;
+    // Whether or not this is a rollback event
+    optional bool is_rollback = 10;
 }
 
 /* Test atom, is not logged anywhere */
@@ -3851,7 +4425,7 @@
         DIALOG_LINE_ITEM = 5;
     }
 
-    optional Type type = 1 [(state_field_option).option = EXCLUSIVE];
+    optional Type type = 1 [(state_field_option).exclusive_state = true];
 
     // Used if the type is LINE_ITEM
     optional string package_name = 2;
@@ -3981,6 +4555,145 @@
     optional State state  = 2;
 }
 
+message MimeTypes {
+    repeated string mime_types = 1;
+}
+
+/**
+ * Logs statistics regarding accesses to external storage.
+ * All stats are normalized for one day period.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
+ */
+message GeneralExternalStorageAccessStats {
+    optional int32 uid = 1 [(is_uid) = true];
+    // Total number of accesses like creation, open, delete and rename/update.
+    // Includes file path and ContentResolver accesses
+    optional uint32 total_accesses = 2;
+    // Number of file path accesses, as opposed to file path and ContentResolver.
+    optional uint32 file_path_accesses = 3;
+    // Number of accesses on secondary volumes like SD cards.
+    // Includes file path and ContentResolver accesses
+    optional uint32 secondary_storage_accesses = 4;
+    // Comma-separated list of mime types that were accessed.
+    optional MimeTypes mime_types_accessed = 5;
+}
+
+/**
+ * Logs when MediaProvider has successfully finished scanning a storage volume.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java
+ */
+message MediaProviderScanOccurred {
+    enum Reason {
+        // Scan triggered due to unknown reason
+        UNKNOWN = 0;
+        // Scan triggered due to storage volume being mounted
+        MOUNTED = 1;
+        // Scan triggered due to explicit user action or app request
+        DEMAND = 2;
+        // Scan triggered due to idle maintenance
+        IDLE = 3;
+    }
+
+    // Volume type that this event pertains to
+    optional android.stats.mediaprovider.VolumeType volume_type = 1;
+    // Reason why this scan was triggered
+    optional Reason reason = 2;
+    // Total number of files scanned
+    optional int64 item_count = 3;
+    // Duration of scan, normalized per file
+    optional float normalized_duration_millis = 4;
+    // Number of database inserts, normalized per file
+    optional float normalized_insert_count = 5;
+    // Number of database updates, normalized per file
+    optional float normalized_update_count = 6;
+    // Number of database deletes, normalized per file
+    optional float normalized_delete_count = 7;
+}
+
+/**
+ * Logs when an app has asked MediaProvider to delete media belonging to the user.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
+ */
+message MediaContentDeleted {
+    // Volume type that this event pertains to
+    optional android.stats.mediaprovider.VolumeType volume_type = 1;
+    // UID of app that requested deletion
+    optional int32 uid = 2 [(is_uid) = true];
+    // Number of items that were deleted
+    optional int32 item_count = 3;
+}
+
+/**
+ * Logs when an app has asked MediaProvider to grant them access to media belonging to the user.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java
+ */
+message MediaProviderPermissionRequested {
+    enum Result {
+        UNKNOWN = 0;
+        USER_GRANTED = 1;
+        AUTO_GRANTED = 2;
+        USER_DENIED = 3;
+        USER_DENIED_WITH_PREJUDICE = 4;
+        AUTO_DENIED = 5;
+    }
+
+    // Volume type that this event pertains to
+    optional android.stats.mediaprovider.VolumeType volume_type = 1;
+    // UID of app that requested permission
+    optional int32 uid = 2 [(is_uid) = true];
+    // Number of items that were requested
+    optional int32 item_count = 3;
+    // Result of this request
+    optional Result result = 4;
+}
+
+/**
+ * Logs when MediaProvider has finished upgrading or downgrading its database schema.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java
+ */
+message MediaProviderSchemaChanged {
+    // Volume type that this event pertains to
+    optional android.stats.mediaprovider.VolumeType volume_type = 1;
+    // Old database version code
+    optional int32 version_from = 2;
+    // New database version code
+    optional int32 version_to = 3;
+    // Total number of files in database
+    optional int64 item_count = 4;
+    // Duration of schema change, normalized per file
+    optional float normalized_duration_millis = 5;
+}
+
+/**
+ * Logs when MediaProvider has finished an idle maintenance job.
+ *
+ * Logged from:
+ *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
+ */
+message MediaProviderIdleMaintenanceFinished {
+    // Volume type that this event pertains to
+    optional android.stats.mediaprovider.VolumeType volume_type = 1;
+
+    // Total number of files in database
+    optional int64 item_count = 2;
+    // Duration of idle maintenance, normalized per file
+    optional float normalized_duration_millis = 3;
+    // Number of thumbnails found to be stale, normalized per file
+    optional float normalized_stale_thumbnails = 4;
+    // Number of items found to be expired, normalized per file
+    optional float normalized_expired_media = 5;
+}
+
 /**
  * Represents boot time event with duration in ms.
  *
@@ -4190,8 +4903,7 @@
  * after an OTA.
  *
  * Logged from:
- *  - system/core/fs_mgr/libsnapshot/snapshot.cpp
- *  - system/core/fs_mgr/libsnapshot/snapshotctl.cpp
+ *  - system/update_engine/cleanup_previous_update_action.cc
  */
 message SnapshotMergeReported {
     // Keep in sync with
@@ -4229,6 +4941,106 @@
     // Number of reboots that occurred after issuing and before completing the
     // merge of all the snapshot devices.
     optional int32 intermediate_reboots = 3;
+
+    // The device has been upgraded to Virtual A/B.
+    optional bool is_vab_retrofit = 4;
+
+    // Space that has been temporarily allocated in the /data partition
+    // containing the dm-snapshot's copy-on-write data generated during a
+    // Virtual A/B update.
+    optional int64 cow_file_size_bytes = 5;
+}
+
+/**
+ * Event representing when BlobStoreManager.Session#commit() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobCommitted {
+    // Uid of the Blob committer
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob committed
+    optional int64 blob_id = 2;
+
+    // Size of the Blob
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Commit Succeeded
+        SUCCESS = 1;
+        // Commit Failed: Error occurred during commit
+        ERROR_DURING_COMMIT = 2;
+        // Commit Failed: Digest of the data did not match Blob digest
+        DIGEST_MISMATCH = 3;
+        // Commit Failed: Allowed count limit exceeded
+        COUNT_LIMIT_EXCEEDED = 4;
+    }
+    optional Result result = 4;
+}
+
+/**
+ * Event representing when BlobStoreManager#acquireLease() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobLeased{
+    // Uid of the Blob leasee
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob leased or 0 if the Blob does not exist
+    optional int64 blob_id = 2;
+
+    // Size of the Blob or 0 if the Blob does not exist
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Lease Succeeded
+        SUCCESS = 1;
+        // Lease Failed: Blob does not exist
+        BLOB_DNE = 2;
+        // Lease Failed: Leasee does not have access to the Blob
+        ACCESS_NOT_ALLOWED = 3;
+        // Lease Failed: Leasee requested an invalid expiry duration
+        LEASE_EXPIRY_INVALID = 4;
+        // Lease Failed: Leasee has exceeded the total data lease limit
+        DATA_SIZE_LIMIT_EXCEEDED = 5;
+        // Leasee Failed: Allowed count limit exceeded
+        COUNT_LIMIT_EXCEEDED = 6;
+    }
+    optional Result result = 4;
+}
+
+/**
+ * Event representing when BlobStoreManager#openBlob() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobOpened{
+    // Uid of the Blob opener
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob opened or 0 if the Blob does not exist
+    optional int64 blob_id = 2;
+
+    // Size of the Blob or 0 if the Blob does not exist
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Open Succeeded
+        SUCCESS = 1;
+        // Open Failed: Blob does not exist
+        BLOB_DNE = 2;
+        // Open Failed: Opener does not have access to the Blob
+        ACCESS_NOT_ALLOWED = 3;
+    }
+    optional Result result = 4;
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -4315,6 +5127,66 @@
 }
 
 /**
+ * Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions.
+ * Note that the data is expected to be sliced into more dimensions in future. In other words,
+ * the caller must not assume any row of data is one full report when filtering with a set of
+ * matching conditions, because future data may represent with multiple rows what is currently
+ * represented by one.
+ * To avoid being broken by future slicing, callers must take care to aggregate rows even if they
+ * query all the existing columns.
+ *
+ * Pulled from:
+ *   StatsPullAtomService (using NetworkStatsService to get NetworkStats)
+ */
+message DataUsageBytesTransfer {
+    // State of this record. Should be NetworkStats#SET_DEFAULT or NetworkStats#SET_FOREGROUND to
+    // indicate the foreground state, or NetworkStats#SET_ALL to indicate the record is for all
+    // states combined, not including debug states. See NetworkStats#SET_*.
+    optional int32 state = 1;
+
+    optional int64 rx_bytes = 2;
+
+    optional int64 rx_packets = 3;
+
+    optional int64 tx_bytes = 4;
+
+    optional int64 tx_packets = 5;
+
+    // Radio Access Technology (RAT) type of this record, should be one of
+    // TelephonyManager#NETWORK_TYPE_* constants, or NetworkTemplate#NETWORK_TYPE_ALL to indicate
+    // the record is for all rat types combined.
+    optional int32 rat_type = 6;
+
+    // Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the
+    // record is combined across subscriptions.
+    optional string sim_mcc = 7;
+    optional string sim_mnc = 8;
+
+    // Allows mobile virtual network operators (MVNOs) to be identified with individual IDs.
+    // See TelephonyManager#getSimCarrierId.
+    optional int32 carrier_id = 9;
+
+    // Enumeration of opportunistic states with an additional ALL state indicates the record is
+    // combined regardless of the boolean value in its field.
+    enum DataSubscriptionState {
+        UNKNOWN = 0; // For server side backward compatibility.
+        ALL = 1;
+        OPPORTUNISTIC = 2;
+        NOT_OPPORTUNISTIC = 3;
+    }
+    // Mark whether the subscription is an opportunistic data subscription, and ALL indicates the
+    // record is combined across opportunistic data subscriptions.
+    // See {@link SubscriptionManager#setOpportunistic}.
+    optional DataSubscriptionState opportunistic_data_sub = 10;
+
+    // Indicate whether NR is connected, server side could use this with RAT type to determine if
+    // the record is for 5G NSA (Non Stand Alone) mode, where the primary cell is still LTE and
+    // network allocates a secondary 5G cell so telephony reports RAT = LTE along with NR state as
+    // connected.
+    optional bool is_nr_connected = 11;
+}
+
+/**
  * Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller.
  *
  * Pulled from:
@@ -4521,8 +5393,8 @@
     optional int64 page_major_fault = 5;
 
     // RSS
-    // Value is read from /proc/PID/status. Or from memory.stat, field
-    // total_rss if per-app memory cgroups are enabled.
+    // Value is read from memory.stat, field total_rss if per-app memory
+    // cgroups are enabled. Otherwise, value from /proc/pid/stat.
     optional int64 rss_in_bytes = 6;
 
     // CACHE
@@ -4532,56 +5404,17 @@
 
     // SWAP
     // Value is read from memory.stat, field total_swap if per-app memory
-    // cgroups are enabled. Otherwise, VmSwap from /proc/PID/status.
+    // cgroups are enabled. Otherwise, 0.
     optional int64 swap_in_bytes = 8;
 
-    // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0.
+    // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always -1.
     optional int64 rss_high_watermark_in_bytes = 9 [deprecated = true];
 
-    // Elapsed real time when the process started.
-    // Value is read from /proc/PID/stat, field 22. 0 if read from per-app memory cgroups.
-    optional int64 start_time_nanos = 10;
+    // Deprecated: use ProcessMemorySnapshot atom instead. Always -1.
+    optional int64 start_time_nanos = 10 [deprecated = true];
 
-    // Anonymous page size plus swap size. Values are read from /proc/PID/status.
-    optional int32 anon_rss_and_swap_in_kilobytes = 11;
-}
-
-/*
- * Logs the memory stats for a native process (from procfs).
- *
- * Pulled from StatsCompanionService for selected native processes.
- */
-message NativeProcessMemoryState {
-    // The uid if available. -1 means not available.
-    optional int32 uid = 1 [(is_uid) = true];
-
-    // The process name.
-    // Value read from /proc/PID/cmdline.
-    optional string process_name = 2;
-
-    // # of page-faults
-    optional int64 page_fault = 3;
-
-    // # of major page-faults
-    optional int64 page_major_fault = 4;
-
-    // RSS
-    // Value read from /proc/PID/status.
-    optional int64 rss_in_bytes = 5;
-
-    // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0.
-    optional int64 rss_high_watermark_in_bytes = 6 [deprecated = true];
-
-    // Elapsed real time when the process started.
-    // Value is read from /proc/PID/stat, field 22.
-    optional int64 start_time_nanos = 7;
-
-    // SWAP
-    // Value read from /proc/PID/status, field VmSwap.
-    optional int64 swap_in_bytes = 8;
-
-    // Anonymous page size plus swap size. Values are read from /proc/PID/status.
-    optional int32 anon_rss_and_swap_in_kilobytes = 9;
+    // Deprecated: use ProcessMemorySnapshot atom instead. Always -1.
+    optional int32 anon_rss_and_swap_in_kilobytes = 11 [deprecated = true];
 }
 
 /*
@@ -4601,9 +5434,53 @@
     // Provided by ActivityManagerService or read from /proc/PID/cmdline.
     optional string process_name = 2;
 
+    // Deprecated: use rss_high_water_mark_in_kilobytes instead. This field is
+    // computed by converting kilobytes to bytes.
+    optional int64 rss_high_water_mark_in_bytes = 3 [deprecated = true];
+
     // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in
     // /proc/PID/status.
-    optional int64 rss_high_water_mark_in_bytes = 3;
+    optional int32 rss_high_water_mark_in_kilobytes = 4;
+}
+
+/*
+ * Logs the memory stats for a process.
+ *
+ * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService)
+ * and for selected native processes.
+ */
+message ProcessMemorySnapshot {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The process name.
+    // Usually package name or process cmdline.
+    // Provided by ActivityManagerService or read from /proc/PID/cmdline.
+    optional string process_name = 2;
+
+    // The pid of the process.
+    // Allows to disambiguate instances of the process.
+    optional int32 pid = 3;
+
+    // The current OOM score adjustment value.
+    // Read from ProcessRecord for managed processes.
+    // Placeholder -1001 (OOM_SCORE_ADJ_MIN - 1, outside of allowed range) for native ones.
+    optional int32 oom_score_adj = 4;
+
+    // The current RSS of the process.
+    // VmRSS from /proc/pid/status.
+    optional int32 rss_in_kilobytes = 5;
+
+    // The current anon RSS of the process.
+    // RssAnon from /proc/pid/status.
+    optional int32 anon_rss_in_kilobytes = 6;
+
+    // The current swap size of the process.
+    // VmSwap from /proc/pid/status.
+    optional int32 swap_in_kilobytes = 7;
+
+    // The sum of rss_in_kilobytes and swap_in_kilobytes.
+    optional int32 anon_rss_and_swap_in_kilobytes = 8;
 }
 
 /*
@@ -5026,48 +5903,81 @@
 }
 
 message AggStats {
-    optional int64 min = 1;
+    // These are all in byte resolution.
+    optional int64 min = 1 [deprecated = true];
+    optional int64 average = 2 [deprecated = true];
+    optional int64 max = 3 [deprecated = true];
 
-    optional int64 average = 2;
-
-    optional int64 max = 3;
+    // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+    // int64 fields.
+    optional int32 mean_kb = 4;
+    optional int32 max_kb = 5;
 }
 
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+    PROCESS_STATE_UNKNOWN = 0;
+    // Persistent system process.
+    PROCESS_STATE_PERSISTENT = 1;
+    // Top activity; actually any visible activity.
+    PROCESS_STATE_TOP = 2;
+    // Process binding to top or a foreground service.
+    PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+    // Processing running a foreground service.
+    PROCESS_STATE_FGS = 4;
+    // Important foreground process (ime, wallpaper, etc).
+    PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+    // Important background process.
+    PROCESS_STATE_BACKGROUND = 6;
+    // Process running a receiver.
+    PROCESS_STATE_RECEIVER = 7;
+    // All kinds of cached processes.
+    PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
 message ProcessStatsStateProto {
     optional android.service.procstats.ScreenState screen_state = 1;
 
-    optional android.service.procstats.MemoryState memory_state = 2;
+    optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
 
     // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
     // and not frameworks/base/core/java/android/app/ActivityManager.java
-    optional android.service.procstats.ProcessState process_state = 3;
+    optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+    optional ProcessStateAggregated process_state_aggregated = 10;
 
     // Millisecond uptime duration spent in this state
-    optional int64 duration_millis = 4;
+    optional int64 duration_millis = 4 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 duration_minutes = 11;
 
     // Millisecond elapsed realtime duration spent in this state
-    optional int64 realtime_duration_millis = 9;
+    optional int64 realtime_duration_millis = 9 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 realtime_duration_minutes = 12;
 
     // # of samples taken
     optional int32 sample_size = 5;
 
     // PSS is memory reserved for this process
-    optional AggStats pss = 6;
+    optional AggStats pss = 6 [deprecated = true];
 
     // USS is memory shared between processes, divided evenly for accounting
-    optional AggStats uss = 7;
+    optional AggStats uss = 7 [deprecated = true];
 
     // RSS is memory resident for this process
     optional AggStats rss = 8;
 }
 
-// Next Tag: 7
+// Next Tag: 8
 message ProcessStatsProto {
     // Name of process.
     optional string process = 1;
 
     // Uid of the process.
-    optional int32 uid = 2;
+    optional int32 uid = 2 [(is_uid) = true];
 
     // Information about how often kills occurred
     message Kill {
@@ -5080,7 +5990,7 @@
         // PSS stats during cached kill
         optional AggStats cached_pss = 3;
     }
-    optional Kill kill = 3;
+    optional Kill kill = 3 [deprecated = true];
 
     // Time and memory spent in various states.
     repeated ProcessStatsStateProto states = 5;
@@ -5088,6 +5998,28 @@
     // Total time process has been running...  screen_state, memory_state, and process_state
     // will not be set.
     optional ProcessStatsStateProto total_running_state = 6;
+
+    // Association data for this process in this state;
+    // each entry here is one association.
+    repeated ProcessStatsAssociationProto assocs = 7;
+}
+
+// Next Tag: 6
+message ProcessStatsAssociationProto {
+    // Procss Name of the associated process (client process of service binding)
+    optional string assoc_process_name = 1;
+
+    // Package Name of the associated package (client package of service binding)
+    optional string assoc_package_name = 2 [deprecated = true];
+
+    // UID of the associated process/package (client package of service binding)
+    optional int32 assoc_uid = 5 [(is_uid) = true];
+
+    // Total count of the times this association (service binding) appeared.
+    optional int32 total_count = 3;
+
+    // Uptime total duration in seconds this association (service binding) was around.
+    optional int32 total_duration_secs = 4;
 }
 
 message PackageServiceOperationStatsProto {
@@ -5236,6 +6168,10 @@
  */
 message ProcStats {
     optional ProcessStatsSectionProto proc_stats_section = 1;
+    // Data pulled from device into this is sometimes sharded across multiple atoms to work around
+    // a size limit. When this happens, this shard ID will contain an increasing 1-indexed integer
+    // with the number of this shard.
+    optional int32 shard_id = 2;
 }
 
 /**
@@ -5263,6 +6199,141 @@
     optional NotificationRemoteViewsProto notification_remote_views = 1;
 }
 
+/**
+ * Atom that contains a list of a package's preferences, pulled from NotificationManagerService.java
+ */
+message PackageNotificationPreferences {
+    // Uid under which the package is installed.
+    optional int32 uid = 1 [(is_uid) = true];
+    // Notification importance, which specifies when and how a notification is displayed.
+    // Specified under core/java/android/app/NotificationManager.java.
+    optional int32 importance = 2;
+    // Lockscreen visibility as set by the user.
+    optional int32 visibility = 3;
+    // Bitfield mask indicating what fields were locked by the user (see LockableAppfields in
+    // PreferencesHelper.java)
+    optional int32 user_locked_fields = 4;
+}
+
+/**
+ * Atom that contains a list of a package's channel preferences, pulled from
+ * NotificationManagerService.java.
+ */
+message PackageNotificationChannelPreferences {
+    // Uid under which the package is installed.
+    optional int32 uid = 1 [(is_uid) = true];
+    // Channel's ID. Should always be available.
+    optional string channel_id = 2;
+    // Channel's name. Should always be available.
+    optional string channel_name = 3;
+    // Channel's description. Optionally set by the channel creator.
+    optional string description = 4;
+    // Notification importance, which specifies when and how a notification is displayed. Specified
+    // under core/java/android/app/NotificationManager.java.
+    optional int32 importance = 5;
+    // Bitmask representing which fields have been set by the user. See field bitmask descriptions
+    // at core/java/android/app/NotificationChannel.java
+    optional int32 user_locked_fields = 6;
+    // Indicates if the channel was deleted by the app.
+    optional bool is_deleted = 7;
+    // Indicates if the channel was marked as a conversation by the app.
+    optional bool is_conversation = 8;
+    // Indicates if the channel is a conversation that was demoted by the user.
+    optional bool is_demoted_conversation = 9;
+    // Indicates if the channel is a conversation that was marked as important by the user.
+    optional bool is_important_conversation = 10;
+}
+
+/**
+ * Atom that represents an item in the list of Do Not Disturb rules, pulled from
+ * NotificationManagerService.java.
+ */
+message DNDModeProto {
+    enum Mode {
+        ROOT_CONFIG = -1;  // Used to distinguish the config (one per user) from the rules.
+        ZEN_MODE_OFF = 0;
+        ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+        ZEN_MODE_NO_INTERRUPTIONS = 2;
+        ZEN_MODE_ALARMS = 3;
+    }
+    optional int32 user = 1;  // Android user ID (0, 1, 10, ...)
+    optional bool enabled = 2;  // true for ROOT_CONFIG if a manualRule is enabled
+    optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG
+    optional Mode zen_mode = 4;
+    // id is one of the system default rule IDs, or empty
+    // May also be "MANUAL_RULE" to indicate app-activation of the manual rule.
+    optional string id = 5;
+    optional int32 uid = 6 [(is_uid) = true]; // currently only SYSTEM_UID or 0 for other
+    optional DNDPolicyProto policy = 7;
+}
+
+/**
+ * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto.
+ */
+message DNDPolicyProto {
+    enum State {
+        STATE_UNSET = 0;
+        STATE_ALLOW = 1;
+        STATE_DISALLOW = 2;
+    }
+    optional State calls = 1;
+    optional State repeat_callers = 2;
+    optional State messages = 3;
+    optional State conversations = 4;
+    optional State reminders = 5;
+    optional State events = 6;
+    optional State alarms = 7;
+    optional State media = 8;
+    optional State system = 9;
+    optional State fullscreen = 10;
+    optional State lights = 11;
+    optional State peek = 12;
+    optional State status_bar = 13;
+    optional State badge = 14;
+    optional State ambient = 15;
+    optional State notification_list = 16;
+
+    enum PeopleType {
+        PEOPLE_UNSET = 0;
+        PEOPLE_ANYONE = 1;
+        PEOPLE_CONTACTS = 2;
+        PEOPLE_STARRED = 3;
+        PEOPLE_NONE = 4;
+    }
+
+    optional PeopleType allow_calls_from = 17;
+    optional PeopleType allow_messages_from = 18;
+
+    enum ConversationType {
+        CONV_UNSET = 0;
+        CONV_ANYONE = 1;
+        CONV_IMPORTANT = 2;
+        CONV_NONE = 3;
+    }
+
+    optional ConversationType allow_conversations_from = 19;
+}
+
+/**
+ * Atom that contains a list of a package's channel group preferences, pulled from
+ * NotificationManagerService.java.
+ */
+message PackageNotificationChannelGroupPreferences {
+    // Uid under which the package is installed.
+    optional int32 uid = 1 [(is_uid) = true];
+    // Channel Group's ID. Should always be available.
+    optional string group_id = 2;
+    // Channel Group's name. Should always be available.
+    optional string group_name = 3;
+    // Channel Group's description. Optionally set by group creator.
+    optional string description = 4;
+    // Indicates if notifications from this channel group are blocked.
+    optional bool is_blocked = 5;
+    // Bitmask representing which fields have been set by the user. See field bitmask descriptions
+    // at core/java/android/app/NotificationChannelGroup.java
+    optional int32 user_locked_fields = 6;
+}
+
 message PowerProfileProto {
     optional double cpu_suspend = 1;
 
@@ -5494,6 +6565,16 @@
         SET_WHITELIST = 3;
         SET_DISABLED = 4;
         ON_USER_DATA_REMOVED = 5;
+        ON_DATA_SHARE_REQUEST = 6;
+        ACCEPT_DATA_SHARE_REQUEST = 7;
+        REJECT_DATA_SHARE_REQUEST = 8;
+        DATA_SHARE_WRITE_FINISHED = 9;
+        DATA_SHARE_ERROR_IOEXCEPTION = 10;
+        DATA_SHARE_ERROR_EMPTY_DATA = 11;
+        DATA_SHARE_ERROR_CLIENT_PIPE_FAIL = 12;
+        DATA_SHARE_ERROR_SERVICE_PIPE_FAIL = 13;
+        DATA_SHARE_ERROR_CONCURRENT_REQUEST = 14;
+        DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 15;
     }
     optional Event event = 1;
     // component/package of content capture service.
@@ -5785,6 +6866,15 @@
     optional int32 repeatedly_pick_times = 7;
 }
 
+/** Logs the drag and drop of files.
+
+ * Logged from:
+ *     package/app/DocumentsUI/src/com/android/documentsui/Metrics.java
+ */
+message DocsUIDragAndDropReported {
+    optional bool drag_initiated_from_docsui = 1;
+}
+
 /**
  * Logs when an app's memory is compacted.
  *
@@ -6431,10 +7521,10 @@
     optional int64 request_id = 1;
 
     // UID of package requesting the permission grant
-    optional int32 requesting_uid = 2 [(is_uid) = true];
+    optional int32 uid = 2 [(is_uid) = true];
 
     // Name of package requesting the permission grant
-    optional string requesting_package_name = 3;
+    optional string package_name = 3;
 
     // The permission to be granted
     optional string permission_name = 4;
@@ -6462,6 +7552,20 @@
         AUTO_DENIED = 8;
         // permission request was ignored because permission is restricted
         IGNORED_RESTRICTED_PERMISSION = 9;
+        // one time permission was granted by user action
+        USER_GRANTED_ONE_TIME = 10;
+        // user ignored request by leaving the request screen without choosing any option
+        USER_IGNORED = 11;
+        // user granted the permission after being linked to settings
+        USER_GRANTED_IN_SETTINGS = 12;
+        // user denied the permission after being linked to settings
+        USER_DENIED_IN_SETTINGS = 13;
+        // user denied the permission with prejudice after being linked to settings
+        USER_DENIED_WITH_PREJUDICE_IN_SETTINGS = 14;
+        // permission was automatically revoked after one-time permission expired
+        AUTO_ONE_TIME_PERMISSION_REVOKED = 15;
+        // permission was automatically revoked for unused app
+        AUTO_UNUSED_APP_PERMISSION_REVOKED = 16;
     }
     // The result of the permission grant
     optional Result result = 6;
@@ -6922,8 +8026,58 @@
 }
 
 /**
- * State of a dangerous permission requested by a package
+ * Track Legacy DRM usage
+ * Logged from
+ *   frameworks/av/drm/drmserver/DrmManager.cpp
  */
+message MediametricsDrmManagerReported {
+    optional int64 timestamp_nanos = 1;
+    optional string package_name = 2;
+    optional int64 package_version_code = 3;
+    optional int64 media_apex_version = 4;
+
+    enum Method {
+        METHOD_NOT_FOUND       = -1;
+        GET_CONSTRAINTS        =  0;
+        GET_METADATA           =  1;
+        CAN_HANDLE             =  2;
+        PROCESS_DRM_INFO       =  3;
+        ACQUIRE_DRM_INFO       =  4;
+        SAVE_RIGHTS            =  5;
+        GET_ORIGINAL_MIME_TYPE =  6;
+        GET_DRM_OBJECT_TYPE    =  7;
+        CHECK_RIGHTS_STATUS    =  8;
+        REMOVE_RIGHTS          =  9;
+        REMOVE_ALL_RIGHTS      = 10;
+        OPEN_CONVERT_SESSION   = 11;
+        OPEN_DECRYPT_SESSION   = 12;
+    }
+
+    // plugin_id+description inform which Legacy DRM plugins are still in use on device
+    optional string plugin_id = 5;
+    optional string description = 6;
+    optional Method method = 7;
+    optional string mime_types = 8;
+
+    optional int64 get_constraints_count =  9;
+    optional int64 get_metadata_count = 10;
+    optional int64 can_handle_count = 11;
+    optional int64 process_drm_info_count = 12;
+    optional int64 acquire_drm_info_count = 13;
+    optional int64 save_rights_count = 14;
+    optional int64 get_original_mime_type_count = 15;
+    optional int64 get_drm_object_type_count = 16;
+    optional int64 check_rights_status_count = 17;
+    optional int64 remove_rights_count = 18;
+    optional int64 remove_all_rights_count = 19;
+    optional int64 open_convert_session_count = 20;
+    optional int64 open_decrypt_session_count = 21;
+}
+
+/**
+ * State of a dangerous permission requested by a package
+ * Pulled from: StatsCompanionService
+*/
 message DangerousPermissionState {
     // Name of the permission
     optional string permission_name = 1;
@@ -6955,7 +8109,8 @@
     optional string method_name = 2;
 
     // True if the package is preinstalled.
-    optional bool is_preinstalled = 3;
+    // Starting from Android 11, this boolean is not set and will always be false.
+    optional bool is_preinstalled = 3 [deprecated = true];
 
     // True if the package is privileged.
     // Starting from Android 11, this boolean is not set and will always be false.
@@ -7003,6 +8158,7 @@
         INSTALL_FAILURE_DOWNLOAD = 23;
         INSTALL_FAILURE_STATE_MISMATCH = 24;
         INSTALL_FAILURE_COMMIT = 25;
+        REBOOT_TRIGGERED = 26;
     }
     optional Status status = 4;
 }
@@ -7131,6 +8287,12 @@
 
     // CPU Vulkan implementation is in use.
     optional bool cpu_vulkan_in_use = 6;
+
+    // App is not doing pre-rotation correctly.
+    optional bool false_prerotation = 7;
+
+    // App creates GLESv1 context.
+    optional bool gles_1_in_use = 8;
 }
 
 /*
@@ -7139,11 +8301,27 @@
  * Pulled from StatsCompanionService.
  */
 message SystemIonHeapSize {
+    // Deprecated due to limited support of ion stats in debugfs.
+    // Use `IonHeapSize` instead.
+    option deprecated = true;
+
     // Size of the system ion heap in bytes.
+    // Read from debugfs.
     optional int64 size_in_bytes = 1;
 }
 
 /*
+ * Logs the total size of the ion heap.
+ *
+ * Pulled from StatsCompanionService.
+ */
+message IonHeapSize {
+    // Total size of all ion heaps in kilobytes.
+    // Read from: /sys/kernel/ion/total_heaps_kb.
+    optional int32 total_size_kb = 1;
+}
+
+/*
  * Logs the per-process size of the system ion heap.
  *
  * Pulled from StatsCompanionService.
@@ -7234,8 +8412,14 @@
  * Logged from the Intelligence mainline module.
  */
 message IntelligenceEventReported {
+  // The event type.
   optional android.stats.intelligence.EventType event_id = 1;
+  // Success, failure.
   optional android.stats.intelligence.Status status = 2;
+  // How many times the event occured (to report a batch of high frequency events).
+  optional int32 count = 3;
+  // How long the event took (sum of durations if count > 1)
+  optional int64 duration_millis = 4;
 }
 
 /**
@@ -7258,6 +8442,245 @@
 }
 
 /**
+ * Logs when Car User Hal is requested to switch/create/remove user.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalModifyUserRequestReported {
+    // Request id for the request.
+    optional int32 request_id = 1;
+    // Request type.
+    enum RequestType {
+        UNKNOWN = 0;
+        // Car user manager requested user switch.
+        SWITCH_REQUEST_ANDROID = 1;
+        // OEM requested User switch.
+        SWITCH_REQUEST_OEM = 2;
+        // Hal switch requested after android switch using activity manager.
+        SWITCH_REQUEST_LEGACY = 3;
+        // Create User
+        CREATE_REQUEST = 4;
+        // Remove User
+        REMOVE_REQUEST = 5;
+    }
+    optional RequestType request_type = 2;
+    // Android User id of the current user which can only be 0, 10, 11 and so on.
+    // -1 if not available.
+    optional int32 user_id = 3;
+    // VHAL flags of the current user. (-1 if not available)
+    optional int32 user_flags = 4;
+    // Android User id of the target user for switch/create/remove. It can only
+    // be 0, 10, 11 and so on. -1 if not available.
+    optional int32 target_user_id = 5;
+    // VHAL flags of the target user for switch/create/remove. (-1 if not available)
+    optional int32 target_user_flags = 6;
+    // Request timeout Milliseconds (-1 if not available)
+    optional int32 timeout_millis = 7;
+}
+
+/**
+ * Logs when Car User Hal responds to switch/create user request.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalModifyUserResponseReported {
+    // Request id of the request associated with the response.
+    optional int32 request_id = 1;
+    // Car user hal callback status.
+    enum CallbackStatus {
+        UNKNOWN = 0;
+        // Hal response was invalid.
+        INVALID = 1;
+        // Hal response was ok.
+        OK = 2;
+        // Hal timeout during set call.
+        HAL_SET_TIMEOUT = 3;
+        // Hal response timeout.
+        HAL_RESPONSE_TIMEOUT = 4;
+        // Hal responded with wrong info.
+        WRONG_HAL_RESPONSE = 5;
+        // Hal is processing multiple requests simultaneously.
+        CONCURRENT_OPERATION = 6;
+    }
+    optional CallbackStatus callback_status = 2;
+
+    // Hal request status for user switch/create/remove.
+    enum HalRequestStatus {
+        UNSPECIFIED = 0;
+        // Hal request for user switch/create is successful.
+        SUCCESS = 1;
+        // Hal request for user switch/create failed.
+        FAILURE = 2;
+    }
+    optional HalRequestStatus request_status = 3;
+}
+
+/**
+ * Logs when post switch response is posted to Car User Hal.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalPostSwitchResponseReported {
+    // Request id.
+    optional int32 request_id = 1;
+
+    // Android user switch status.
+    enum UserSwitchStatus {
+        UNKNOWN = 0;
+        // Android user switch is successful.
+        SUCCESS = 1;
+        // Android user switch failed.
+        FAILURE = 2;
+    }
+    optional UserSwitchStatus switch_status = 2;
+}
+
+/**
+ * Logs when initial user information is requested from Car User Hal.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalInitialUserInfoRequestReported {
+    // Request id for the request.
+    optional int32 request_id = 1;
+
+    // Request type for initial user information.
+    enum InitialUserInfoRequestType {
+        UNKNOWN = 0;
+        // At the first time Android was booted (or after a factory reset).
+        FIRST_BOOT = 1;
+        // At the first time Android was booted after the system was updated.
+        FIRST_BOOT_AFTER_OTA = 2;
+        // When Android was booted "from scratch".
+        COLD_BOOT = 3;
+        // When Android was resumed after the system was suspended to memory.
+        RESUME = 4;
+    }
+    optional InitialUserInfoRequestType request_type = 2;
+    // Request timeout Milliseconds (-1 if not available)
+    optional int32 timeout_millis = 3;
+}
+
+/**
+ * Logs when Car User Hal responds to initial user information requests.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalInitialUserInfoResponseReported {
+    // Request id of the request associated with the response.
+    optional int32 request_id = 1;
+    // Car user hal callback status.
+    enum CallbackStatus {
+        UNKNOWN = 0;
+        // Hal response was invalid.
+        INVALID = 1;
+        // Hal response was ok.
+        OK = 2;
+        // Hal timeout during set call.
+        HAL_SET_TIMEOUT = 3;
+        // Hal response timeout.
+        HAL_RESPONSE_TIMEOUT = 4;
+        // Hal responded with wrong info.
+        WRONG_HAL_RESPONSE = 5;
+        // Hal is processing multiple requests simultaneously.
+        CONCURRENT_OPERATION = 6;
+    }
+    optional CallbackStatus callback_status = 2;
+    // Response for initial user information request.
+    enum InitialUserInfoResponseAction {
+        UNSPECIFIED = 0;
+        // Let the Android System decide what to do.
+        DEFAULT = 1;
+        // Switch to an existing Android user.
+        SWITCH = 2;
+        // Create a new Android user (and switch to it).
+        CREATE = 3;
+    }
+    optional InitialUserInfoResponseAction response_action = 3;
+    // Android User id of the target user which can only be 0, 10, 11 and so on.
+    // -1 if not available.
+    optional int32 target_user = 4;
+    // VHAL flags of the current user. (-1 if not available)
+    optional int32 target_user_flags = 5;
+    // User locales
+    optional string user_locales = 6;
+}
+
+/**
+ * Logs when set user association is requested from Car User Hal.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalUserAssociationRequestReported {
+    // Request id for the request.
+    optional int32 request_id = 1;
+    // Request type.
+    enum RequestType {
+        UNKNOWN = 0;
+        // For setting user association information.
+        SET = 1;
+        // For getting user association information.
+        GET = 2;
+    }
+    optional RequestType request_type = 2;
+    // Android User id of the current user which can only be 0, 10, 11 and so on.
+    // -1 if not available.
+    optional int32 current_user_id = 3;
+    // VHAL flags of the current user. (-1 if not available)
+    optional int32 current_user_flags = 4;
+    // Number of the set associations requested.
+    optional int32 number_associations = 5;
+    // Concatenated string for the types from set associations request.
+    // This is a string converted from an array of integers.
+    optional string user_identification_association_types = 6;
+    // Concatenated string for the values from set associations request.
+    // This is a string converted from an array of integers.
+    optional string user_identification_association_values = 7;
+}
+
+/**
+ * Logs when Car User Hal responds to set user association requests.
+ *
+ * Logged from:
+ *   packages/services/Car/service/src/com/android/car/hal/UserHalService.java
+ */
+message CarUserHalSetUserAssociationResponseReported {
+    // Request id of the request associated with the response.
+    optional int32 request_id = 1;
+    // Car user hal callback status.
+    enum CallbackStatus {
+        UNKNOWN = 0;
+        // Hal response was invalid.
+        INVALID = 1;
+        // Hal response was ok.
+        OK = 2;
+        // Hal timeout during set call.
+        HAL_SET_TIMEOUT = 3;
+        // Hal response timeout.
+        HAL_RESPONSE_TIMEOUT = 4;
+        // Hal responded with wrong info.
+        WRONG_HAL_RESPONSE = 5;
+        // Hal is processing multiple requests simultaneously.
+        CONCURRENT_OPERATION = 6;
+    }
+    optional CallbackStatus callback_status = 2;
+    // Number of the set associations in the response.
+    optional int32 number_associations = 3;
+    // Concatenated string for the types from set associations request.
+    // This is a string converted from an array of integers.
+    optional string user_identification_association_types = 4;
+    // Concatenated string for the values from set associations request.
+    // This is a string converted from an array of integers.
+    optional string user_identification_association_values = 5;
+}
+
+/**
  * Logs whether GarageMode is entered.
  *
  * Logged from:
@@ -7275,11 +8698,11 @@
     // Uid of the package requesting the op
     optional int32 uid = 1 [(is_uid) = true];
 
-    // Nmae of the package performing the op
+    // Name of the package performing the op
     optional string package_name = 2;
 
-    // operation id; maps to the OP_* constants in AppOpsManager.java
-    optional int32 op_id = 3;
+    // operation id
+    optional android.app.AppOpEnum op_id = 3 [default = APP_OP_NONE];
 
     // The number of times the op was granted while the app was in the
     // foreground (only for trusted requests)
@@ -7304,6 +8727,58 @@
     // For long-running operations, total duration of the operation
     // while the app was in the background (only for trusted requests)
     optional int64 trusted_background_duration_millis = 9;
+
+    // Whether AppOps is guarded by Runtime permission
+    optional bool is_runtime_permission = 10;
+}
+
+/**
+ * Historical app ops data per package and attribution tag.
+ */
+message AttributedAppOps {
+    // Uid of the package requesting the op
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Name of the package performing the op
+    optional string package_name = 2;
+
+    // tag; provided by developer when accessing related API, limited at 50 chars by API.
+    // Attributions must be provided through manifest using <attribution> tag available in R and
+    // above.
+    optional string tag = 3;
+
+    // operation id
+    optional android.app.AppOpEnum op = 4 [default = APP_OP_NONE];
+
+    // The number of times the op was granted while the app was in the
+    // foreground (only for trusted requests)
+    optional int64 trusted_foreground_granted_count = 5;
+
+    // The number of times the op was granted while the app was in the
+    // background (only for trusted requests)
+    optional int64 trusted_background_granted_count = 6;
+
+    // The number of times the op was rejected while the app was in the
+    // foreground (only for trusted requests)
+    optional int64 trusted_foreground_rejected_count = 7;
+
+    // The number of times the op was rejected while the app was in the
+    // background (only for trusted requests)
+    optional int64 trusted_background_rejected_count = 8;
+
+    // For long-running operations, total duration of the operation
+    // while the app was in the foreground (only for trusted requests)
+    optional int64 trusted_foreground_duration_millis = 9;
+
+    // For long-running operations, total duration of the operation
+    // while the app was in the background (only for trusted requests)
+    optional int64 trusted_background_duration_millis = 10;
+
+    // Whether AppOps is guarded by Runtime permission
+    optional bool is_runtime_permission = 11;
+
+    // Sampling rate used on device, from 0 to 100
+    optional int32 sampling_rate = 12;
 }
 
 /**
@@ -7418,6 +8893,9 @@
 
     // Button clicked by user - same as bit flags in buttons_presented with only single bit set
     optional int32 button_clicked = 5;
+
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 6;
 }
 
 /**
@@ -7470,6 +8948,28 @@
 
     // The result of the permission grant
     optional bool permission_granted = 6;
+
+    // State of Permission Flags after grant as per android.content.pm.PermissionFlags
+    optional int32 permission_flags = 7;
+
+    enum Button {
+        UNDEFINED = 0;
+        // Allow button
+        ALLOW = 1;
+        // Deny button
+        DENY = 2;
+        // Ask every time button
+        ASK_EVERY_TIME = 3;
+        // Allow all the time button
+        ALLOW_ALWAYS = 4;
+        // Allow only while using the app button
+        ALLOW_FOREGROUND = 5;
+        // Same is Deny button but shown in while in use dialog
+        DENY_FOREGROUND = 6;
+    }
+
+    // Button pressed in the dialog
+    optional Button button_pressed = 8;
 }
 
 /**
@@ -7490,7 +8990,8 @@
 }
 
 /**
-* Information about a AppPermissionsFragment viewed by user
+* Information about a AppPermissionGroupsFragment viewed by user. Fragment has been renamed, but
+* the log retains the old fragment name.
 */
 message AppPermissionsFragmentViewed {
     // id which identifies single session of user interacting with permission controller
@@ -7518,7 +9019,6 @@
     }
     optional Category category = 6;
 }
-
 /**
 * Information about a PermissionAppsFragment viewed by user.
 * Logged from ui/handheld/PermissionAppsFragment.java
@@ -7551,6 +9051,478 @@
 }
 
 /**
+* Log that the Auto Revoke notification has been clicked
+* Logged from ui/ManagePermissionsActivity
+*/
+message AutoRevokeNotificationClicked {
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 1;
+}
+
+/**
+* Log that an app has been displayed on the auto revoke page, and lists one permission that was
+* auto revoked for it.
+* Logged from ui/handheld/AutoRevokeFragment
+*/
+message AutoRevokeFragmentAppViewed {
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 1;
+
+    // UID of package for which permissions are viewed
+    optional int32 uid = 2 [(is_uid) = true];
+
+    // Name of package for which permissions are viewed
+    optional string package_name = 3;
+
+    // The name of a permission group that has been revoked
+    optional string permission_group_name = 4;
+
+    // The age of the app- more than three months old, or more than six months
+    enum Age {
+        UNDEFINED = 0;
+        NEWER_BUCKET = 1;
+        OLDER_BUCKET = 2;
+    }
+
+    // How long the app has been unused. Currently, newer bucket is 3 months, older is 6 months
+    optional Age age = 5;
+}
+
+/**
+* Log that the user has interacted with an app on the auto revoke fragment
+* Logged from ui/handheld/AutoRevokeFragment
+*/
+message AutoRevokedAppInteraction {
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 1;
+
+    // UID of package for which permissions are viewed
+    optional int32 uid = 2 [(is_uid) = true];
+
+    // Name of package for which permissions are viewed
+    optional string package_name = 3;
+
+    enum Action {
+        UNDEFINED = 0;
+        REMOVE = 1;
+        OPEN = 2;
+        APP_INFO = 3;
+        PERMISSIONS = 4;
+        REMOVE_IN_SETTINGS = 5;
+        OPEN_IN_SETTINGS = 6;
+    }
+
+    // The action the user took to interact with the app
+    optional Action action = 4;
+}
+
+/**
+* Log that the AppPermissionGroupsFragment has been interacted with for the possible purposes of
+* auto revoke, or that the auto revoke switch has been changed
+* Logged from ui/handheld/AppPermissionGroupsFragment
+ */
+message AppPermissionGroupsFragmentAutoRevokeAction {
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 1;
+
+    // UID of package for which permissions are viewed
+    optional int32 uid = 2 [(is_uid) = true];
+
+    // Name of package for which permissions are viewed
+    optional string package_name = 3;
+
+    enum Action {
+        UNDEFINED = 0;
+        OPENED_FOR_AUTO_REVOKE = 1;
+        OPENED_FROM_INTENT = 2;
+        SWITCH_ENABLED = 3;
+        SWITCH_DISABLED = 4;
+    }
+
+    // The action the user took to interact with the fragment
+    optional Action action = 4;
+}
+
+/**
+ * Logs when there is a smart selection related event.
+ * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java
+ * Logged from: TextClassifierEventLogger.java
+ */
+message TextSelectionEvent {
+    // A session ID.
+    optional string session_id = 1;
+
+    // Event type of this event.
+    optional android.stats.textclassifier.EventType event_type = 2;
+
+    // Name of the annotator model that is involved in this event.
+    optional string model_name = 3;
+
+    // Type of widget that was involved in triggering this event.
+    optional android.stats.textclassifier.WidgetType widget_type = 4;
+
+    // Index of this event in a session.
+    optional int32 event_index = 5;
+
+    // Entity type that is involved.
+    optional string entity_type = 6;
+
+    // Relative word index of the start of the selection.
+    optional int32 relative_word_start_index = 7;
+
+    // Relative word (exclusive) index of the end of the selection.
+    optional int32 relative_word_end_index = 8;
+
+    // Relative word index of the start of the smart selection.
+    optional int32 relative_suggested_word_start_index = 9;
+
+    // Relative word (exclusive) index of the end of the smart selection.
+    optional int32 relative_suggested_word_end_index = 10;
+
+    // Name of source package.
+    optional string package_name = 11;
+
+    // Name of the LangID model that is involved in this event.
+    optional string langid_model_name = 12;
+}
+
+/**
+ * Logs when there is a smart linkify related event.
+ * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java
+ * Logged from: TextClassifierEventLogger.java
+ */
+message TextLinkifyEvent {
+    // A session ID.
+    optional string session_id = 1;
+
+    // Event type of this event.
+    optional android.stats.textclassifier.EventType event_type = 2;
+
+    // Name of the annotator model that is involved in this event.
+    optional string model_name = 3;
+
+    // Type of widget that was involved in triggering this event.
+    optional android.stats.textclassifier.WidgetType widget_type = 4;
+
+    // Index of this event in a session.
+    optional int32 event_index = 5;
+
+    // Entity type that is involved.
+    optional string entity_type = 6;
+
+    // Number of links detected.
+    optional int32 num_links = 7;
+
+    // The total length of all links.
+    optional int32 linked_text_length = 8;
+
+    // Length of input text.
+    optional int32 text_length = 9;
+
+    // Time spent on generating links in ms.
+    optional int64 latency_millis = 10;
+
+    // Name of source package.
+    optional string package_name = 11;
+
+    // Name of the LangID model that is involved in this event.
+    optional string langid_model_name = 12;
+}
+
+/**
+ * Logs when there is a conversation actions related event.
+ * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java
+ * Logged from: TextClassifierEventLogger.java
+ */
+message ConversationActionsEvent {
+    // A session ID.
+    optional string session_id = 1;
+
+    // Event type of this event.
+    optional android.stats.textclassifier.EventType event_type = 2;
+
+    // Name of the actions model that is involved in this event.
+    optional string model_name = 3;
+
+    // Type of widget that was involved in triggering this event.
+    optional android.stats.textclassifier.WidgetType widget_type = 4;
+
+    // The first entity type that is involved.
+    optional string first_entity_type = 5;
+
+    // The second entity type that is involved.
+    optional string second_entity_type = 6;
+
+    // The third entity type that is involved.
+    optional string third_entity_type = 7;
+
+    // The score of the first entity type.
+    optional float score = 8;
+
+    // Name of source package.
+    optional string package_name = 9;
+
+    // Name of the annotator model that is involved in this event.
+    optional string annotator_model_name = 10;
+
+    // Name of the LangID model that is involved in this event.
+    optional string langid_model_name = 11;
+}
+
+/**
+ * Logs when there is a language detection related event.
+ * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java
+ * Logged from: TextClassifierEventLogger.java
+ */
+message LanguageDetectionEvent {
+    // A session ID.
+    optional string session_id = 1;
+
+    // Event type of this event.
+    optional android.stats.textclassifier.EventType event_type = 2;
+
+    // Name of the language detection model that is involved in this event.
+    optional string model_name = 3;
+
+    // Type of widget that was involved in triggering this event.
+    optional android.stats.textclassifier.WidgetType widget_type = 4;
+
+    // Detected language.
+    optional string language_tag = 5;
+
+    // Score of the detected language.
+    optional float score = 6;
+
+    // Position of this action.
+    optional int32 action_index = 7;
+
+    // Name of source package.
+    optional string package_name = 8;
+}
+
+/**
+ * Information about an OTA update attempt by update_engine.
+ * Logged from platform/system/update_engine/metrics_reporter_android.cc
+ */
+message UpdateEngineUpdateAttemptReported {
+    // The number of attempts for the update engine to apply a given payload.
+    optional int32 attempt_number = 1;
+
+    optional android.stats.otaupdate.PayloadType payload_type = 2;
+
+    // The total time in minutes for the update engine to apply a given payload.
+    // The time is calculated by calling clock_gettime() / CLOCK_BOOTTIME; and
+    // it's increased when the system is sleeping.
+    optional int32 duration_boottime_in_minutes = 3;
+
+    // The total time in minutes for the update engine to apply a given payload.
+    // The time is calculated by calling clock_gettime() / CLOCK_MONOTONIC_RAW;
+    // and it's not increased when the system is sleeping.
+    optional int32 duration_monotonic_in_minutes = 4;
+
+    // The size of the payload in MiBs.
+    optional int32 payload_size_mib = 5;
+
+    // The attempt result reported by the update engine for an OTA update.
+    optional android.stats.otaupdate.AttemptResult attempt_result = 6;
+
+    // The error code reported by the update engine after an OTA update attempt
+    // on A/B devices.
+    optional android.stats.otaupdate.ErrorCode error_code = 7;
+
+    // The build fingerprint of the source system. The value is read from a
+    // system property when the device takes the update. e.g.
+    // Android/aosp_sailfish/sailfish:10/QP1A.190425.004/5507117:userdebug/test-keys
+    optional string source_fingerprint = 8;
+
+    // Size of super partition.
+    optional int64 super_partition_size_bytes = 9;
+
+    // Size of current slot within the super partition.
+    optional int64 slot_size_bytes = 10;
+
+    // Free space available in the super partition.
+    optional int64 super_free_space_bytes = 11;
+}
+
+/**
+ * Information about all the attempts the device make before finishing the
+ * successful update.
+ * Logged from platform/system/update_engine/metrics_reporter_android.cc
+ */
+message UpdateEngineSuccessfulUpdateReported {
+    // The number of attempts for the update engine to apply the payload for a
+    // successful update.
+    optional int32 attempt_count = 1;
+
+    optional android.stats.otaupdate.PayloadType payload_type = 2;
+
+    optional int32 payload_size_mib = 3;
+
+    // The total number of bytes downloaded by update_engine since the last
+    // successful update.
+    optional int32 total_bytes_downloaded_mib = 4;
+
+    // The ratio in percentage of the over-downloaded bytes compared to the
+    // total bytes needed to successfully install the update. e.g. 200 if we
+    // download 200MiB in total for a 100MiB package.
+    optional int32 download_overhead_percentage = 5;
+
+    // The total time in minutes for the update engine to apply the payload for a
+    // successful update.
+    optional int32 total_duration_minutes = 6;
+
+    // The number of reboot of the device during a successful update.
+    optional int32 reboot_count = 7;
+}
+
+/**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+    optional bool successful = 1;
+}
+
+/**
+ * Global display pipeline metrics reported by SurfaceFlinger.
+ * Pulled from:
+ *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
+ */
+message SurfaceflingerStatsGlobalInfo {
+    // Total number of frames presented during the tracing period
+    optional int64 total_frames = 1;
+    // Total number of frames missed
+    optional int64 missed_frames = 2;
+    // Total number of frames that fell back to client composition
+    optional int64 client_composition_frames = 3;
+    // Total time the display was turned on
+    optional int64 display_on_millis = 4;
+    // Total time that was spent performing animations.
+    // This is derived from the present-to-present layer histogram
+    optional int64 animation_millis = 5;
+    // Total number of event connections tracked by SurfaceFlinger at the time
+    // of this pull. If this number grows prohibitively large, then this can
+    // cause jank due to resource contention.
+    optional int32 event_connection_count = 6;
+    // Set of timings measured from when SurfaceFlinger began compositing a
+    // frame, until the frame was requested to be presented to the display. This
+    // measures SurfaceFlinger's total CPU walltime on the critical path per
+    // frame.
+    optional FrameTimingHistogram frame_duration = 7
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when SurfaceFlinger first began using the
+    // GPU to composite a frame, until the GPU has finished compositing that
+    // frame. This measures the total additional time SurfaceFlinger needed to
+    // perform due to falling back into GPU composition.
+    optional FrameTimingHistogram render_engine_timing = 8
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
+/**
+ * Per-layer display pipeline metrics reported by SurfaceFlinger.
+ * The number of layers uploaded will be restricted due to size limitations.
+ * Pulled from:
+ *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
+ */
+message SurfaceflingerStatsLayerInfo {
+    // The layer for this set of metrics
+    // For now we can infer that the package name is included in the layer
+    // name.
+    optional string layer_name = 1;
+    // Total number of frames presented
+    optional int64 total_frames = 2;
+    // Total number of dropped frames while latching a buffer for this layer.
+    optional int64 dropped_frames = 3;
+    // Set of timings measured between successive presentation timestamps.
+    optional FrameTimingHistogram present_to_present = 4
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when an app queued a buffer for
+    // presentation, until the buffer was actually presented to the
+    // display.
+    optional FrameTimingHistogram post_to_present = 5
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when a buffer is ready to be presented,
+    // until the buffer was actually presented to the display.
+    optional FrameTimingHistogram acquire_to_present = 6
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when a buffer was latched by
+    // SurfaceFlinger, until the buffer was presented to the display
+    optional FrameTimingHistogram latch_to_present = 7
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from the desired presentation to the actual
+    // presentation time
+    optional FrameTimingHistogram desired_to_present = 8
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when an app queued a buffer for
+    // presentation, until the buffer was ready to be presented.
+    optional FrameTimingHistogram post_to_acquire = 9
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Frames missed latch because the acquire fence didn't fire
+    optional int64 late_acquire_frames = 10;
+    // Frames latched early because the desired present time was bad
+    optional int64 bad_desired_present_frames = 11;
+}
+
+/**
+ * Histogram of frame counts bucketed by time in milliseconds.
+ * Because of size limitations, we hard-cap the number of buckets, with
+ * buckets for corresponding to larger milliseconds being less precise.
+ */
+message FrameTimingHistogram {
+    // Timings in milliseconds that describes a set of histogram buckets
+    repeated int32 time_millis_buckets = 1;
+    // Number of frames that match to each time_millis, i.e. the bucket
+    // contents
+    // It's required that len(time_millis) == len(frame_count)
+    repeated int64 frame_counts = 2;
+}
+
+/**
+ * Janky event as reported by SurfaceFlinger.
+ * This event is intended to be consumed by a Perfetto subscriber for
+ * automated trace collection.
+ *
+ * Logged from:
+ *    frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
+ */
+message DisplayJankReported {
+    // Informational field for how long the janky event lasted in milliseconds
+    optional int64 event_duration_millis = 1;
+    // Number of frame deadlines missed, where SurfaceFlinger failed to update
+    // the display on time.
+    optional int32 present_deadlines_missed = 2;
+}
+
+/**
+ * Information about camera facing and API level usage.
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java
+ */
+message CameraActionEvent {
+    // Camera session duration
+    optional int64 duration_millis = 1;
+
+    // Camera API level used
+    optional int32 api_level = 2;
+
+    // Name of client package
+    optional string package_name = 3;
+
+    // Camera facing
+    enum Facing {
+        UNKNOWN = 0;
+        BACK = 1;
+        FRONT = 2;
+        EXTERNAL = 3;
+    }
+    optional Facing facing = 4;
+}
+
+/**
  * Logs when a compatibility change is affecting an app.
  *
  * Logged from:
@@ -7583,6 +9555,7 @@
 
     // Where it was logged from.
     optional Source source = 4;
+
 }
 
 /**
@@ -7649,70 +9622,123 @@
 }
 
 /**
- * Information about an OTA update attempt by update_engine.
- * Logged from platform/system/update_engine/metrics_reporter_android.cc
- */
-message UpdateEngineUpdateAttemptReported {
-    // The number of attempts for the update engine to apply a given payload.
-    optional int32 attempt_number = 1;
+ * State of a dangerous permission requested by a package - sampled
+ * Pulled from: StatsCompanionService.java with data obtained from PackageManager API
+*/
+message DangerousPermissionStateSampled {
+    // Name of the permission
+    optional string permission_name = 1;
 
-    optional android.stats.otaupdate.PayloadType payload_type = 2;
+    // Uid of the package
+    optional int32 uid = 2 [(is_uid) = true];
 
-    // The total time in minutes for the update engine to apply a given payload.
-    // The time is calculated by calling clock_gettime() / CLOCK_BOOTTIME; and
-    // it's increased when the system is sleeping.
-    optional int32 duration_boottime_in_minutes = 3;
+    // If the permission is granted to the uid
+    optional bool is_granted = 3;
 
-    // The total time in minutes for the update engine to apply a given payload.
-    // The time is calculated by calling clock_gettime() / CLOCK_MONOTONIC_RAW;
-    // and it's not increased when the system is sleeping.
-    optional int32 duration_monotonic_in_minutes = 4;
-
-    // The size of the payload in MiBs.
-    optional int32 payload_size_mib = 5;
-
-    // The attempt result reported by the update engine for an OTA update.
-    optional android.stats.otaupdate.AttemptResult attempt_result = 6;
-
-    // The error code reported by the update engine after an OTA update attempt
-    // on A/B devices.
-    optional android.stats.otaupdate.ErrorCode error_code = 7;
-
-    // The build fingerprint of the source system. The value is read from a
-    // system property when the device takes the update. e.g.
-    // Android/aosp_sailfish/sailfish:10/QP1A.190425.004/5507117:userdebug/test-keys
-    optional string source_fingerprint = 8;
+    // Permission flags as per android.content.pm.PermissionFlags
+    optional int32 permission_flags = 4;
 }
 
 /**
- * Information about all the attempts the device make before finishing the
- * successful update.
- * Logged from platform/system/update_engine/metrics_reporter_android.cc
+ * HWUI stats for a given app.
  */
-message UpdateEngineSuccessfulUpdateReported {
-    // The number of attempts for the update engine to apply the payload for a
-    // successful update.
-    optional int32 attempt_count = 1;
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
 
-    optional android.stats.otaupdate.PayloadType payload_type = 2;
+    // The version code of the app
+    optional int64 version_code = 2;
 
-    optional int32 payload_size_mib = 3;
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 start_millis = 3;
 
-    // The total number of bytes downloaded by update_engine since the last
-    // successful update.
-    optional int32 total_bytes_downloaded_mib = 4;
+    optional int64 end_millis = 4;
 
-    // The ratio in percentage of the over-downloaded bytes compared to the
-    // total bytes needed to successfully install the update. e.g. 200 if we
-    // download 200MiB in total for a 100MiB package.
-    optional int32 download_overhead_percentage = 5;
+    // HWUI renders pipeline type: GL (1) or Vulkan (2).
+    enum PipelineType {
+        UNKNOWN = 0;
+        GL = 1;
+        VULKAN = 2;
+    }
 
-    // The total time in minutes for the update engine to apply the payload for a
-    // successful update.
-    optional int32 total_duration_minutes = 6;
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
 
-    // The number of reboot of the device during a successful update.
-    optional int32 reboot_count = 7;
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
+
+/**
+ * Message related to dangerous (runtime) app ops access
+ */
+message RuntimeAppOpAccess {
+    // Uid of the package accessing app op
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Name of the package accessing app op
+    optional string package_name = 2;
+
+    // deprecated - set to empty string
+    optional string op_deprecated = 3 [deprecated = true];
+
+    // attribution_tag; provided by developer when accessing related API, limited at 50 chars by
+    // API. Attributions must be provided through manifest using <attribution> tag available in R
+    // and above.
+    optional string attribution_tag = 4;
+
+    // message related to app op access, limited to 600 chars by API
+    optional string message = 5;
+
+    enum SamplingStrategy {
+        DEFAULT = 0;
+        UNIFORM = 1;
+        RARELY_USED = 2;
+        BOOT_TIME_SAMPLING = 3;
+        UNIFORM_OPS = 4;
+    }
+
+    // sampling strategy used to collect this message
+    optional SamplingStrategy sampling_strategy = 6;
+
+    // operation id
+    optional android.app.AppOpEnum op = 7 [default = APP_OP_NONE];
 }
 
 /*
@@ -7755,6 +9781,274 @@
     optional UserEncryptionState user_encryption_state = 3;
 }
 
+/*
+ * Logs integrity check information during each install.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+ */
+message IntegrityCheckResultReported {
+    optional string package_name = 1;
+    optional string app_certificate_hash = 2;
+    optional int64 version_code = 3;
+    optional string installer_package_name = 4;
+    enum Response {
+        UNKNOWN = 0;
+        ALLOWED = 1;
+        REJECTED = 2;
+        FORCE_ALLOWED = 3;
+    }
+    optional Response response = 5;
+    // An estimate on the cause of the response. This will only be populated for
+    // REJECTED and FORCE_ALLOWED
+    optional bool caused_by_app_cert_rule = 6;
+    optional bool caused_by_installer_rule = 7;
+}
+
+/**
+ * Logs the information about the rules and the provider whenever rules are
+ * pushed into AppIntegrityManager.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+ */
+message IntegrityRulesPushed {
+    optional bool success = 1;
+    // Package name of the app that pushed the rules.
+    optional string rule_provider = 2;
+    // Version string of arbitrary format provided by the rule provider to
+    // identify the rules.
+    optional string rule_version = 3;
+}
+
+/**
+ * Logs when a cell broadcast message is received on the device.
+ *
+ * Logged from Cell Broadcast module and platform:
+ *   packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/
+ *   packages/apps/CellBroadcastReceiver/
+ *   frameworks/opt/telephony/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
+ */
+message CellBroadcastMessageReported {
+    // The type of Cell Broadcast message
+    enum CbType {
+        UNKNOWN_TYPE = 0;
+        GSM = 1;
+        CDMA = 2;
+        CDMA_SPC = 3;
+    }
+
+    // The parts of the cell broadcast message pipeline
+    enum ReportSource {
+        UNKNOWN_SOURCE = 0;
+        FRAMEWORK = 1;
+        CB_SERVICE = 2;
+        CB_RECEIVER_APP = 3;
+    }
+
+    // GSM, CDMA, CDMA-SCP
+    optional CbType type = 1;
+
+    // The source of the report
+    optional ReportSource source = 2;
+}
+
+/**
+ * Logs when a cell broadcast message is filtered out, or otherwise intentionally not sent to CBR.
+ *
+ * Logged from CellBroadcastService module:
+ *   packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/
+ */
+message CellBroadcastMessageFiltered {
+    enum FilterReason {
+        NOT_FILTERED = 0;
+        DUPLICATE_MESSAGE = 1;
+        GEOFENCED_MESSAGE = 2;
+        AREA_INFO_MESSAGE = 3;
+    }
+
+    // GSM, CDMA, CDMA-SCP
+    optional CellBroadcastMessageReported.CbType type = 1;
+
+    // The source of the report
+    optional FilterReason filter = 2;
+}
+
+/**
+ * Logs when an error occurs while handling a cell broadcast message;
+ *
+ * Logged from CellBroadcastService module:
+ *   packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/
+ */
+message CellBroadcastMessageError {
+    // The type of error raised when trying to handle a cell broadcast message
+    enum ErrorType {
+        UNKNOWN_TYPE = 0;
+        CDMA_DECODING_ERROR = 1;
+        CDMA_SCP_EMPTY = 2;
+        CDMA_SCP_HANDLING_ERROR = 3;
+        GSM_INVALID_HEADER_LENGTH = 4;
+        GSM_UNSUPPORTED_HEADER_MESSAGE_TYPE = 5;
+        GSM_UNSUPPORTED_HEADER_DATA_CODING_SCHEME = 6;
+        GSM_INVALID_PDU = 7;
+        GSM_INVALID_GEO_FENCING_DATA = 8;
+        GSM_UMTS_INVALID_WAC = 9;
+        FAILED_TO_INSERT_TO_DB = 10;
+        UNEXPECTED_GEOMETRY_FROM_FWK = 11;
+        UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK = 12;
+        UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK = 13;
+        UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK = 14;
+        NO_CONNECTION_TO_CB_SERVICE = 15;
+    }
+
+    // What kind of error occurred
+    optional ErrorType type = 1;
+
+    // Exception message (or log message) associated with the error (max 1000 chars)
+    optional string exception_message = 2;
+}
+
+/**
+ * Logs when a tune occurs through device's Frontend.
+ * This is atom ID 276.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/tv/tuner/Tuner.java
+ */
+message TvTunerStateChanged {
+    enum State {
+        UNKNOWN = 0;
+        TUNING = 1; // Signal is tuned
+        LOCKED = 2;    // the signal is locked
+        NOT_LOCKED = 3; // the signal isn’t locked.
+        SIGNAL_LOST = 4; // the signal was locked, but is lost now.
+        SCANNING = 5; // the signal is scanned
+        SCAN_STOPPED = 6; // the scan is stopped.
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    //  new state
+    optional State state = 2;
+}
+
+/**
+ * Logs the status of a dvr playback or record.
+ * This is atom ID 279.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/tv/tuner/dvr
+ */
+message TvTunerDvrStatus {
+    enum Type {
+        UNKNOWN_TYPE = 0;
+        PLAYBACK = 1; // is a playback
+        RECORD = 2; // is a record
+    }
+    enum State {
+        UNKNOWN_STATE = 0;
+        STARTED = 1; // DVR is started
+        STOPPED = 2; // DVR is stopped
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    // DVR type
+    optional Type type = 2;
+    //  DVR state
+    optional State state = 3;
+    //  Identify the segment of a record or playback
+    optional int32 segment_id = 4;
+    // indicate how many overflow or underflow happened between started to stopped
+    optional int32 overflow_underflow_count = 5;
+}
+
+/**
+ * Logs when a cas session opened through MediaCas.
+ * This is atom ID 280.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/MediaCas.java
+ */
+message TvCasSessionOpenStatus {
+    enum State {
+        UNKNOWN = 0;
+        SUCCEEDED = 1; // indicate that the session is opened successfully.
+        FAILED = 2; // indicate that the session isn’t opened successfully.
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    //  Cas system Id
+    optional int32 cas_system_id = 2;
+    // State of the session
+    optional State state = 3;
+}
+
+/**
+ * Logs for ContactsProvider general usage.
+ * This is atom ID 301.
+ *
+ * Logged from:
+ *   packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java
+ */
+message ContactsProviderStatusReported {
+    enum ApiType {
+        UNKNOWN_API = 0;
+        QUERY = 1;
+        // INSERT includes insert and bulkInsert, and inserts triggered by applyBatch.
+        INSERT = 2;
+        // UPDATE and DELETE includes update/delete and the ones triggered by applyBatch.
+        UPDATE = 3;
+        DELETE = 4;
+    }
+
+    enum ResultType {
+        UNKNOWN_RESULT = 0;
+        SUCCESS = 1;
+        FAIL = 2;
+        ILLEGAL_ARGUMENT = 3;
+        UNSUPPORTED_OPERATION = 4;
+    }
+
+    enum CallerType {
+        UNSPECIFIED_CALLER_TYPE = 0;
+        CALLER_IS_SYNC_ADAPTER = 1;
+        CALLER_IS_NOT_SYNC_ADAPTER = 2;
+    }
+
+    optional ApiType api_type = 1;
+    // Defined in
+    // packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java
+    optional int32 uri_type = 2;
+    optional CallerType caller_type = 3;
+    optional ResultType result_type = 4;
+    optional int32 result_count = 5;
+    optional int64 latency_micros = 6;
+}
+
+/**
+ * Logs when an app is frozen or unfrozen.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
+ */
+message AppFreezeChanged {
+    // The type of event.
+    enum Action {
+        UNKNOWN = 0;
+        FREEZE_APP = 1;
+        UNFREEZE_APP = 2;
+    }
+    optional Action action = 1;
+
+    // Pid of the process being frozen.
+    optional int32 pid = 2;
+
+    // Name of the process being frozen.
+    optional string process_name = 3;
+
+    // Time since last unfrozen.
+    optional int64 time_unfrozen_millis = 4;
+}
+
 /**
  * Pulls information for a single voice call.
  *
@@ -7906,6 +10200,808 @@
 }
 
 /**
+ * Logs gnss stats from location service provider
+ *
+ * Pulled from:
+ *  frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+ */
+
+message GnssStats {
+    // Number of location reports since boot
+    optional int64 location_reports = 1;
+
+    // Total pulled reports of Location failures since boot
+    optional int64 location_failure_reports = 2;
+
+    // Number of time to first fix reports since boot
+    optional int64 time_to_first_fix_reports = 3;
+
+    // Total pulled reported time to first fix (in milli-seconds) since boot
+    optional int64 time_to_first_fix_millis = 4;
+
+    // Number of position accuracy reports since boot
+    optional int64 position_accuracy_reports = 5;
+
+    // Total pulled reported position accuracy (in meters) since boot
+    optional int64 position_accuracy_meters = 6;
+
+    // Number of top 4 average CN0 reports since boot
+    optional int64 top_four_average_cn0_reports = 7;
+
+    // Total pulled reported of top 4 average CN0 (dB-mHz) since boot
+    optional int64 top_four_average_cn0_db_mhz = 8;
+
+    // Number of l5 top 4 average CN0 reports since boot
+    optional int64 l5_top_four_average_cn0_reports = 9;
+
+    // Total pulled reported of l5 top 4 average CN0 (dB-mHz) since boot
+    optional int64 l5_top_four_average_cn0_db_mhz = 10;
+
+    // Total number of sv status messages reports since boot
+    optional int64 sv_status_reports = 11;
+
+    // Total number of sv status messages reports, where sv is used in fix since boot
+    optional int64 sv_status_reports_used_in_fix = 12;
+
+    // Total number of L5 sv status messages reports since boot
+    optional int64 l5_sv_status_reports = 13;
+
+    // Total number of L5 sv status messages reports, where sv is used in fix since boot
+    optional int64 l5_sv_status_reports_used_in_fix = 14;
+}
+
+/**
+ * Logs when an app is moved to a different standby bucket.
+ *
+ * Logged from:
+ *   frameworks/base/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+ */
+message AppStandbyBucketChanged {
+    optional string package_name = 1;
+
+    // Should be 0, 10, 11, 12, etc. where 0 is the owner. See UserHandle for more documentation.
+    optional int32 user_id = 2;
+
+    // These enum values match the constants defined in UsageStatsManager.java.
+    enum Bucket {
+        BUCKET_UNKNOWN = 0;
+        BUCKET_EXEMPTED = 5;
+        BUCKET_ACTIVE = 10;
+        BUCKET_WORKING_SET = 20;
+        BUCKET_FREQUENT = 30;
+        BUCKET_RARE = 40;
+        BUCKET_RESTRICTED = 45;
+        BUCKET_NEVER = 50;
+    }
+    optional Bucket bucket = 3;
+
+    enum MainReason {
+        MAIN_UNKNOWN = 0;
+        MAIN_DEFAULT = 0x0100;
+        MAIN_TIMEOUT = 0x0200;
+        MAIN_USAGE = 0x0300;
+        MAIN_FORCED_BY_USER = 0x0400;
+        MAIN_PREDICTED = 0x0500;
+        MAIN_FORCED_BY_SYSTEM = 0x0600;
+    }
+    optional MainReason main_reason = 4;
+
+    // A more detailed reason for the standby bucket change. The sub reason name is dependent on
+    // the main reason. Values are one of the REASON_SUB_XXX constants defined in
+    // UsageStatsManager.java.
+    optional int32 sub_reason = 5;
+}
+
+/**
+* Reports a started sharesheet transaction.
+*
+* Logged from:
+*   frameworks/base/core/java/com/android/internal/app/ChooserActivity.java
+*/
+message SharesheetStarted {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The calling app's package name.
+    optional string package_name = 2;
+    // An identifier to tie together multiple logs relating to the same share event
+    optional int32 instance_id = 3;
+    // The mime type of the share
+    optional string mime_type = 4;
+    // The number of direct targets the calling app is providing that will be shown.
+    optional int32 num_app_provided_direct_targets = 5;
+    // The number of app targets the calling app is providing that will be shown.
+    optional int32 num_app_provided_app_targets = 6;
+    // True if the share originates from the workprofile
+    optional bool is_workprofile = 7;
+
+    enum SharesheetPreviewType {  // Constants from ChooserActivity.java
+        CONTENT_PREVIEW_TYPE_UNKNOWN = 0;  // Default for proto 2 / 3 compatibility.
+        CONTENT_PREVIEW_IMAGE = 1;  // The preview shown in the sharesheet is an image.
+        CONTENT_PREVIEW_FILE = 2;  // The preview shown in the sharesheet is a file.
+        CONTENT_PREVIEW_TEXT = 3;  // The preview shown in the sharesheet is text.
+    }
+    // How the sharesheet preview is presented.
+    optional SharesheetPreviewType preview_type = 8;
+
+    enum ResolverActivityIntent { // Intents handled by ResolverActivity.java
+        INTENT_DEFAULT = 0;
+        INTENT_ACTION_VIEW = 1;
+        INTENT_ACTION_EDIT = 2;
+        INTENT_ACTION_SEND = 3;
+        INTENT_ACTION_SENDTO = 4;
+        INTENT_ACTION_SEND_MULTIPLE = 5;
+        INTENT_ACTION_IMAGE_CAPTURE = 6;
+        INTENT_ACTION_MAIN = 7;
+    }
+    // The intent being processed (only SEND and SEND_MULTIPLE are system sharesheet)
+    optional ResolverActivityIntent intent_type = 9;
+}
+
+/**
+ * Reports a ranking selection event.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/app/ChooserActivity.java (sharesheet)
+ */
+message RankingSelected {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The relevant app's package name (can be source or picked package).
+    optional string package_name = 2;
+    // An identifier to tie together multiple logs relating to the same share event.
+    optional int32 instance_id = 3;
+    // Which of the ranked targets got picked, default starting position 0.
+    optional int32 position_picked = 4;
+}
+
+/**
+ * Logs when TvSettings UI is interacted at.
+ *
+ * Logged from: packages/apps/TvSettings
+ */
+message TvSettingsUIInteracted {
+
+    /** The UI action category */
+    optional android.app.tvsettings.Action action = 1;
+
+    /** The ID of the entry that the users actioned on */
+    optional android.app.tvsettings.ItemId item_id = 2;
+}
+
+/**
+ * Logs information about a package installation using package installer V2 APIs.
+ *
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
+ */
+message PackageInstallerV2Reported {
+    // Whether this installation uses Incremental File System
+    optional bool is_incremental = 1;
+    // Name of the package that is intended to be installed
+    optional string package_name = 2;
+    // The duration between when the install was requested to when the install has completed
+    optional int64 duration_millis = 3;
+    // Installation result in final integer, which are SystemApi's.
+    // Return_code 1 indicates success.
+    // For full list, see frameworks/base/core/java/android/content/pm/PackageManager.java
+    optional int32 return_code  = 4;
+    // Total size of the APKs installed for this package
+    optional int64 apks_size_bytes = 5;
+}
+
+/**
+ * Logs settings provider values.
+ *
+ * Use DeviceConfig.getProperties to get a list Setting key, query the data from content provider,
+ * then write the value to proto.
+ *
+ */
+message SettingSnapshot {
+
+    // Setting key
+    optional string name = 1;
+
+    enum SettingsValueType {
+        NOTASSIGNED = 0;
+        ASSIGNED_BOOL_TYPE = 1;
+        ASSIGNED_INT_TYPE = 2;
+        ASSIGNED_FLOAT_TYPE = 3;
+        ASSIGNED_STRING_TYPE = 4;
+    };
+    // Setting value type
+    optional SettingsValueType type = 2;
+
+    optional bool bool_value = 3;
+
+    optional int32 int_value = 4;
+
+    optional float float_value = 5;
+
+    optional string str_value = 6;
+
+    // Android user index. 0 for primary user, 10, 11 for secondary or profile user
+    optional int32 user_id = 7;
+}
+
+/**
+ * An event logged to indicate that a user journey is about to be performed. This atom includes
+ * relevant information about the users involved in the journey. A UserLifecycleEventOccurred event
+ * will immediately follow this atom which will describe the event(s) and its state.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/UserController.java
+ *   frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
+ */
+message UserLifecycleJourneyReported {
+    // An identifier to track a chain of user lifecycle events occurring (referenced in the
+    // UserLifecycleEventOccurred atom)
+    optional int64 session_id = 1;
+
+    // Indicates what type of user journey this session is related to
+    enum Journey {
+        UNKNOWN = 0; // Undefined user lifecycle journey
+        USER_SWITCH_UI = 1; // A user switch journey where a UI is shown
+        USER_SWITCH_FG = 2; // A user switch journey without a UI shown
+        USER_START = 3; // A user start journey
+        USER_CREATE = 4; // A user creation journey
+    }
+    optional Journey journey = 2;
+    // Which user the journey is originating from - could be -1 for certain phases (eg USER_CREATE)
+    // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest)
+    optional int32 origin_user = 3;
+    // Which user the journey is targeting
+    // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest)
+    optional int32 target_user = 4;
+
+    // What is the user type of the target user
+    // These should be in sync with USER_TYPE_* flags defined in UserManager.java
+    enum UserType {
+        TYPE_UNKNOWN = 0;
+        FULL_SYSTEM = 1;
+        FULL_SECONDARY = 2;
+        FULL_GUEST = 3;
+        FULL_DEMO = 4;
+        FULL_RESTRICTED = 5;
+        PROFILE_MANAGED = 6;
+        SYSTEM_HEADLESS = 7;
+    }
+    optional UserType user_type = 5;
+    // What are the flags attached to the target user
+    optional int32 user_flags = 6;
+}
+
+/**
+ * An event logged when a specific user lifecycle event is performed. These events should be
+ * correlated with a UserLifecycleJourneyReported atom via the session_id.
+ * Note: journeys can span over multiple events, hence some events may share a single session id.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/UserController.java
+ *   frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
+ */
+message UserLifecycleEventOccurred {
+    // An id which links back to user details (reported in the UserLifecycleJourneyReported atom)
+    optional int64 session_id = 1;
+    // The target user for this event (same as target_user in the UserLifecycleJourneyReported atom)
+    // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest)
+    optional int32 user_id = 2;
+
+    enum Event {
+        UNKNOWN = 0; // Indicates that the associated user journey timed-out or resulted in an error
+        SWITCH_USER = 1; // Indicates that this is a user switch event
+        START_USER = 2; // Indicates that this is a user start event
+        CREATE_USER = 3; // Indicates that this is a user create event
+        USER_RUNNING_LOCKED = 4; // Indicates that user is running in locked state
+        UNLOCKING_USER = 5; // Indicates that this is a user unlocking event
+        UNLOCKED_USER = 6; // Indicates that this is a user unlocked event
+    }
+    optional Event event = 3;
+
+    enum State {
+        NONE = 0; // Indicates the associated event has no start/end defined
+        BEGIN = 1;
+        FINISH = 2;
+    }
+    optional State state = 4; // Represents the state of an event (beginning/ending)
+}
+
+/**
+ * Logs when accessibility shortcut clicked.
+ *
+ * Logged from:
+ *   frameworks/base/services/accessibility/java/com/android/server/accessibility
+ */
+message AccessibilityShortcutReported {
+    // The accessibility feature(including installed a11y service, framework a11y feature,
+    // and installed a11y activity) package name that is assigned to the accessibility shortcut.
+    optional string package_name = 1;
+
+    // The definition of the accessibility shortcut.
+    // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto.
+    optional android.stats.accessibility.ShortcutType shortcut_type = 2;
+
+    // The definition of the service status.
+    // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto.
+    optional android.stats.accessibility.ServiceStatus service_status = 3;
+}
+
+/**
+ * Logs when accessibility service status changed.
+ *
+ * Logged from:
+ *   packages/apps/Settings/src/com/android/settings/accessibility
+ */
+message AccessibilityServiceReported {
+    // The accessibility service package name.
+    optional string package_name = 1;
+
+    // The definition of the service status.
+    // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto.
+    optional android.stats.accessibility.ServiceStatus service_status = 2;
+}
+
+/**
+ * Logs when display wake up.
+ *
+ * Logged from:
+ *   services/core/java/com/android/server/power/Notifier.java
+ */
+
+message DisplayWakeReported {
+    // Wake_up_reason code
+    // If LOWORD(wake_up_reason) = 0
+    //     reference to HIWORD(wake_up_reason) PowerManager.WAKE_REASON_XXX
+    //     else reference wake_up_reason to
+    //     services/core/java/com/android/server/power/Notifier.java#onWakeUp
+    optional int32 wake_up_reason = 1;
+}
+
+/**
+ * Logs app usage events.
+ */
+message AppUsageEventOccurred {
+    optional int32 uid = 1 [(is_uid) = true];
+    optional string package_name = 2;
+    optional string class_name = 3;
+
+    enum EventType {
+        NONE = 0;
+        MOVE_TO_FOREGROUND = 1;
+        MOVE_TO_BACKGROUND = 2;
+    }
+    optional EventType event_type = 4;
+}
+
+/*
+ * Quality metrics logged when EVS cameras are active.
+ *
+ * Logged from:
+ *  packages/services/Car/evs/manager/1.1/Enumerator.cpp
+ */
+message EvsUsageStatsReported {
+
+    // Camera identifier to distinguish the source camera device.  This is not
+    // globally unique and therefore cannot be used to identify the user and/or
+    // the device.
+    optional int32 device_id = 1;
+
+    // Peak number of clients during the service
+    optional int32 peak_num_clients = 2;
+
+    // Number of erroneous events during the service
+    optional int32 num_errors = 3;
+
+    // Round trip latency of the very first frame
+    optional int64 first_latency_millis = 4;
+
+    // Average frame round trip latency
+    optional float avg_latency_millis = 5;
+
+    // Peak frame round trip latency
+    optional int64 peak_latency_millis = 6;
+
+    // Total number of frames received
+    optional int64 total_frames = 7;
+
+    // Number of frames ignored
+    optional int64 ignored_frames = 8;
+
+    // Number of dropped frames to synchronize camera devices
+    optional int64 dropped_frames_to_sync = 9;
+
+    // The duration of the service
+    optional int64 duration_millis = 10;
+}
+
+/**
+ * Logs audio power usage stats.
+ *
+ * Pushed from:
+ *  frameworks/av/services/mediametrics/AudioPowerUsage.cpp
+ */
+message AudioPowerUsageDataReported {
+    /**
+     * Device used for input/output
+     *
+     * All audio devices please refer to below file:
+     * system/media/audio/include/system/audio-base.h
+     *
+     * Define our own enum values because we don't report all audio devices.
+     * Currently, we only report built-in audio devices such as handset, speaker,
+     * built-in mics, common audio devices such as wired headset, usb headset
+     * and bluetooth devices.
+     */
+    enum AudioDevice {
+        OUTPUT_EARPIECE         = 0x1; // handset
+        OUTPUT_SPEAKER          = 0x2; // dual speaker
+        OUTPUT_WIRED_HEADSET    = 0x4; // 3.5mm headset
+        OUTPUT_USB_HEADSET      = 0x8; // usb headset
+        OUTPUT_BLUETOOTH_SCO    = 0x10; // bluetooth sco
+        OUTPUT_BLUETOOTH_A2DP   = 0x20; // a2dp
+        OUTPUT_SPEAKER_SAFE     = 0x40; // bottom speaker
+
+        INPUT_DEVICE_BIT        = 0x40000000; // non-negative positive int32.
+        INPUT_BUILTIN_MIC       = 0x40000001; // buildin mic
+        INPUT_BUILTIN_BACK_MIC  = 0x40000002; // buildin back mic
+        INPUT_WIRED_HEADSET_MIC = 0x40000004; // 3.5mm headset mic
+        INPUT_USB_HEADSET_MIC   = 0x40000008; // usb headset mic
+        INPUT_BLUETOOTH_SCO     = 0x40000010; // bluetooth sco mic
+    }
+    optional AudioDevice audio_device = 1;
+
+    // Duration of the audio in seconds
+    optional int32 duration_secs = 2;
+
+    // Average volume (0 ... 1.0)
+    optional float average_volume = 3;
+
+    enum AudioType {
+        UNKNOWN_TYPE = 0;
+        VOICE_CALL_TYPE = 1; // voice call
+        VOIP_CALL_TYPE = 2; // voip call, including uplink and downlink
+        MEDIA_TYPE = 3; // music and system sound
+        RINGTONE_NOTIFICATION_TYPE = 4; // ringtone and notification
+        ALARM_TYPE = 5; // alarm type
+        // record type
+        CAMCORDER_TYPE = 6; // camcorder
+        RECORD_TYPE = 7;  // other recording
+    }
+    optional AudioType type = 4;
+}
+
+/**
+  * Pulls bytes transferred over WiFi and mobile networks sliced by uid, is_metered, and tag.
+  *
+  * Pulled from:
+  *   StatsPullAtomService, which uses NetworkStatsService to query NetworkStats.
+  */
+message BytesTransferByTagAndMetered {
+    optional int32 uid = 1 [(is_uid) = true];
+
+    optional bool is_metered = 2;
+
+    optional int32 tag = 3;
+
+    optional int64 rx_bytes = 4;
+
+    optional int64 rx_packets = 5;
+
+    optional int64 tx_bytes = 6;
+
+    optional int64 tx_packets = 7;
+}
+
+/*
+ * Logs when the Media Output Switcher finishes a media switch operation.
+ *
+ * Logged from:
+ *  packages/apps/Settings/src/com/android/settings/media/MediaOutputSliceWorker.java
+ */
+message MediaOutputOpSwitchReported {
+    // Source medium type before switching.
+    optional android.app.settings.mediaoutput.MediumType source = 1;
+
+    // Target medium type after switching.
+    optional android.app.settings.mediaoutput.MediumType target = 2;
+
+    // The result of switching.
+    optional android.app.settings.mediaoutput.SwitchResult result = 3;
+
+    // The detail code of a switching result.
+    optional android.app.settings.mediaoutput.SubResult subresult = 4;
+
+    /*
+     * The package name of a pre-installed app, whose media session is being switched.
+     */
+    optional string media_session_package_name = 5;
+
+    // The amount of available wired devices when a switching is being performed.
+    optional int32 available_wired_device_count = 6;
+
+    // The amount of available Bluetooth devices a switching is being performed.
+    optional int32 available_bt_device_count = 7;
+
+    // The amount of available remote devices when a switching is being performed.
+    optional int32 available_remote_device_count = 8;
+
+    // The amount of applied devices within a remote dynamic group after a switching is done.
+    optional int32 applied_device_count_within_remote_group = 9;
+}
+
+/**
+ * Logs when the Assistant is invoked.
+ *
+ * Logged from:
+ *   frameworks/base/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+ */
+message AssistantInvocationReported {
+
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+
+    // The registered Assistant's uid and package (as for UiEventReported).
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+
+    // An identifier used to disambiguate which logs refer to a particular invocation of the
+    // Assistant  (as for UiEventReported).
+    optional int32 instance_id = 4;
+
+    // The state of the device at the time of invocation.
+    enum DeviceState {
+        UNKNOWN_DEVICE_STATE = 0;
+        AOD1 = 1;
+        AOD2 = 2;
+        BOUNCER = 3;
+        UNLOCKED_LOCKSCREEN = 4;
+        LAUNCHER_HOME = 5;
+        LAUNCHER_OVERVIEW = 6;
+        LAUNCHER_ALL_APPS = 7;
+        APP_DEFAULT = 8;
+        APP_IMMERSIVE = 9;
+        APP_FULLSCREEN = 10;
+    }
+    optional DeviceState device_state = 5;
+
+    // Whether the Assistant handles were showing at the time of invocation.
+    optional bool assistant_handles_showing = 6;
+}
+
+/**
+ * Logs when an AudioRecord finishes running on an audio device
+ *
+ * Logged from:
+ *   frameworks/av/services/mediametrics/AudioAnalytics.cpp
+ */
+message MediametricsAudioRecordDeviceUsageReported {
+    // The devices connected to this AudioRecord.
+    // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2".
+    // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // See audio_device_t in system/media/audio/include/system/audio-base.h
+    optional string devices = 1;
+
+    // The name of the remote device attached to the device, typically available for USB or BT.
+    // This may be empty for a fixed device, or separated by "|" if more than one.
+    optional string device_names = 2;
+
+    // The amount of time spent in the device as measured by the active track in AudioFlinger.
+    optional int64 device_time_nanos = 3;
+
+    // The audio data format used for encoding.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t
+    optional string encoding = 4;
+
+    // The client-server buffer framecount.
+    // The framecount is generally between 960 - 48000 for PCM encoding.
+    // The framecount represents raw buffer size in bytes for non-PCM encoding.
+    optional int32 frame_count = 5;
+
+    // The number of audio intervals (contiguous, continuous playbacks).
+    optional int32 interval_count = 6;
+
+    // The sample rate of the AudioRecord.
+    // A number generally between 8000-96000 (frames per second).
+    optional int32 sample_rate = 7;
+
+    // The audio input flags used to construct the AudioRecord.
+    // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t
+    optional string flags = 8;
+
+    // The santized package name of the audio client associated with the AudioRecord.
+    // See getSanitizedPackageNameAndVersionCode() in
+    // frameworks/av/services/mediametrics/MediaMetricsService.cpp
+    optional string package_name = 9;
+
+    // The selected device id (nonzero if a non-default device is selected)
+    optional int32 selected_device_id = 10;
+
+    // The caller of the AudioRecord.
+    // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    optional string caller = 11;
+
+    // The audio source for AudioRecord.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_source_t
+    optional string source = 12;
+}
+
+/**
+ * Logs when an AudioThread finishes running on an audio device
+ *
+ * Logged from:
+ *   frameworks/av/services/mediametrics/AudioAnalytics.cpp
+ */
+message MediametricsAudioThreadDeviceUsageReported {
+    // The devices connected to this audio thread.
+    // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2".
+    // (for record threads):
+    // See lookup<INPUT_DEVICE> in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // (for playback threads):
+    // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // See audio_device_t in system/media/audio/include/system/audio-base.h
+    optional string devices = 1;
+
+    // The name of the remote device attached to the device, typically available for USB or BT.
+    // This may be empty for a fixed device, or separated by "|" if more than one.
+    optional string device_names = 2;
+
+    // The amount of time spent in the device as measured by the active track in AudioFlinger.
+    optional int64 device_time_nanos = 3;
+
+    // The audio data format used for encoding.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t
+    optional string encoding = 4;
+
+    // The framecount of the buffer delivered to (or from) the HAL.
+    // The framecount is generally ~960 for PCM encoding.
+    // The framecount represents raw buffer size in bytes for non-PCM encoding.
+    optional int32 frame_count = 5;
+
+    // The number of audio intervals (contiguous, continuous playbacks).
+    optional int32 interval_count = 6;
+
+    // The sample rate of the audio thread.
+    // A number generally between 8000-96000 (frames per second).
+    optional int32 sample_rate = 7;
+
+    // The audio flags used to construct the thread
+    // (for record threads):
+    // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t
+    // (for playback threads):
+    // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t
+    optional string flags = 8;
+
+    // The number of underruns encountered for a playback thread or the
+    // number of overruns encountered for a capture thread.
+    optional int32 xruns = 9;
+
+    // The type of thread
+    // A thread type enumeration from
+    // frameworks/av/mediametrics/services/Translate.h
+    optional string type = 10;
+}
+
+/**
+ * Logs when an AudioTrack finishes running on an audio device
+ *
+ * Logged from:
+ *   frameworks/av/services/mediametrics/AudioAnalytics.cpp
+ */
+message MediametricsAudioTrackDeviceUsageReported {
+    // The output devices connected to this AudioTrack.
+    // A string OR of various output device categories, e.g. "DEVICE1|DEVICE2".
+    // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // See audio_device_t in system/media/audio/include/system/audio-base.h
+    optional string devices = 1;
+
+    // The name of the remote device attached to the device, typically available for USB or BT.
+    // This may be empty for a fixed device, or separated by "|" if more than one.
+    optional string device_names = 2;
+
+    // The amount of time spent in the device as measured by the active track in AudioFlinger.
+    optional int64 device_time_nanos = 3;
+
+    // The audio data format used for encoding.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t
+    optional string encoding = 4;
+
+    // The client-server buffer framecount.
+    // The framecount is generally between 960 - 48000 for PCM encoding.
+    // The framecount represents raw buffer size in bytes for non-PCM encoding.
+    // A static track (see traits) may have a very large framecount.
+    optional int32 frame_count = 5;
+
+    // The number of audio intervals (contiguous, continuous playbacks).
+    optional int32 interval_count = 6;
+
+    // The sample rate of the AudioTrack.
+    // A number generally between 8000-96000 (frames per second).
+    optional int32 sample_rate = 7;
+
+    // The audio flags used to construct the AudioTrack.
+    // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t
+    optional string flags = 8;
+
+    // The number of underruns encountered.
+    optional int32 xruns = 9;
+
+    // The santized package name of the audio client associated with the AudioTrack.
+    // See getSanitizedPackageNameAndVersionCode() in
+    // frameworks/av/services/mediametrics/MediaMetricsService.cpp
+    optional string package_name = 10;
+
+    // The latency of the last sample in the buffer in milliseconds.
+    optional float device_latency_millis = 11;
+
+    // The startup time in milliseconds from start() to sample played.
+    optional float device_startup_millis = 12;
+
+    // The average volume of the track on the device [ 0.f - 1.f ]
+    optional float device_volume = 13;
+
+    // The selected device id (nonzero if a non-default device is selected)
+    optional int32 selected_device_id = 14;
+
+    // The stream_type category for the AudioTrack.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_stream_type_t
+    optional string stream_type = 15;
+
+    // The usage for the AudioTrack.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_usage_t
+    optional string usage = 16;
+
+    // The content type of the AudioTrack.
+    // An enumeration from system/media/audio/include/system/audio-base.h audio_content_type_t
+    optional string content_type = 17;
+
+    // The caller of the AudioTrack.
+    // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    optional string caller = 18;
+
+    // The traits of the AudioTrack.
+    // A string OR of different traits, may be empty string.
+    // Only "static" is supported for R.
+    // See lookup<TRACK_TRAITS>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    optional string traits = 19;
+}
+
+/**
+ * Logs the status of an audio device connection attempt.
+ *
+ * Logged from:
+ *   frameworks/av/services/mediametrics/AudioAnalytics.cpp
+ */
+message MediametricsAudioDeviceConnectionReported {
+    // The input devices represented by this report.
+    // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2".
+    // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // See audio_device_t in system/media/audio/include/system/audio-base.h
+    optional string input_devices = 1;
+
+    // The output devices represented by this report.
+    // A string OR of various output device categories.
+    // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp
+    // See audio_device_t in system/media/audio/include/system/audio-base.h
+    optional string output_devices = 2;
+
+    // The name of the remote device attached to the device, typically available for USB or BT.
+    // This may be empty for a fixed device, or separated by "|" if more than one.
+    optional string device_names = 3;
+
+    // The result of the audio device connection.
+    // 0 indicates success: connection verified.
+    // 1 indicates unknown: connection not verified or not known if diverted properly.
+    // Other values indicate specific status.
+    // See DeviceConnectionResult in frameworks/av/services/mediametrics/AudioTypes.h
+    optional int32 result = 4;
+
+    // Average milliseconds of time to connect
+    optional float time_to_connect_millis = 5;
+
+    // Number of connections if aggregated statistics, otherwise 1.
+    optional int32 connection_count = 6;
+}
+
+/**
  * Logs: i) creation of different types of cryptographic keys in the keystore,
  * ii) operations performed using the keys,
  * iii) attestation of the keys
@@ -7999,9 +11095,9 @@
     optional KeyBlobUsageRequirements key_blob_usage_reqs = 11;
 
     enum Type {
-        KEY_OPERATION = 0;
-        KEY_CREATION = 1;
-        KEY_ATTESTATION = 2;
+        key_operation = 0;
+        key_creation = 1;
+        key_attestation = 2;
     }
     /** Key creation event, operation event or attestation event? */
     optional Type type = 12;
@@ -8012,3 +11108,72 @@
     /** Response code or error code */
     optional int32 error_code = 14;
 }
+
+// Blob Committer stats
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobCommitterProto {
+    // Committer app's uid
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Unix epoch timestamp of the commit in milliseconds
+    optional int64 commit_timestamp_millis = 2;
+
+    // Flags of what access types the committer has set for the Blob
+    optional int32 access_mode = 3;
+
+    // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST
+    optional int32 num_whitelisted_package = 4;
+}
+
+// Blob Leasee stats
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobLeaseeProto {
+    // Leasee app's uid
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Unix epoch timestamp for lease expiration in milliseconds
+    optional int64 lease_expiry_timestamp_millis = 2;
+}
+
+// List of Blob Committers
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobCommitterListProto {
+    repeated BlobCommitterProto committer = 1;
+}
+
+// List of Blob Leasees
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobLeaseeListProto {
+    repeated BlobLeaseeProto leasee = 1;
+}
+
+/**
+ * Logs the current state of a Blob committed with BlobStoreManager
+ *
+ * Pulled from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobInfo {
+    // Id of the Blob
+    optional int64 blob_id = 1;
+
+    // Size of the Blob data
+    optional int64 size = 2;
+
+    // Unix epoch timestamp of the Blob's expiration in milliseconds
+    optional int64 expiry_timestamp_millis = 3;
+
+    // List of committers of this Blob
+    optional BlobCommitterListProto committers = 4;
+
+    // List of leasees of this Blob
+    optional BlobLeaseeListProto leasees = 5;
+}
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index cf0cc3d..e9875ba 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -37,7 +37,8 @@
 bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig,
                                        const vector<sp<ConditionTracker>>& allConditionTrackers,
                                        const unordered_map<int64_t, int>& conditionIdIndexMap,
-                                       vector<bool>& stack) {
+                                       vector<bool>& stack,
+                                       vector<ConditionState>& initialConditionCache) {
     VLOG("Combination predicate init() %lld", (long long)mConditionId);
     if (mInitialized) {
         return true;
@@ -73,15 +74,15 @@
             return false;
         }
 
-
-        bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
-                                                     conditionIdIndexMap, stack);
+        bool initChildSucceeded =
+                childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap,
+                                   stack, initialConditionCache);
 
         if (!initChildSucceeded) {
             ALOGW("Child initialization failed %lld ", (long long)child);
             return false;
         } else {
-            ALOGW("Child initialization success %lld ", (long long)child);
+            VLOG("Child initialization success %lld ", (long long)child);
         }
 
         if (allConditionTrackers[childIndex]->isSliced()) {
@@ -95,6 +96,11 @@
                              childTracker->getLogTrackerIndex().end());
     }
 
+    mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation,
+                                                          initialConditionCache);
+    initialConditionCache[mIndex] =
+            evaluateCombinationCondition(mChildren, mLogicalOperation, initialConditionCache);
+
     // unmark this node in the recursion stack.
     stack[mIndex] = false;
 
@@ -105,20 +111,14 @@
 
 void CombinationConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const std::vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
         const bool isPartialLink,
-        vector<ConditionState>& conditionCache,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
+        vector<ConditionState>& conditionCache) const {
     // So far, this is fine as there is at most one child having sliced output.
     for (const int childIndex : mChildren) {
         if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
             allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
-                                                      dimensionFields,
-                                                      isSubOutputDimensionFields,
                                                       isPartialLink,
-                                                      conditionCache,
-                                                      dimensionsKeySet);
+                                                      conditionCache);
         }
     }
     conditionCache[mIndex] =
@@ -147,17 +147,14 @@
     ConditionState newCondition =
             evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
     if (!mSliced) {
+        bool nonSlicedChanged = (mUnSlicedPartCondition != newCondition);
+        mUnSlicedPartCondition = newCondition;
 
-        bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
-        mNonSlicedConditionState = newCondition;
-
-        nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
-
+        nonSlicedConditionCache[mIndex] = mUnSlicedPartCondition;
         conditionChangedCache[mIndex] = nonSlicedChanged;
-        mUnSlicedPart = newCondition;
     } else {
-        mUnSlicedPart = evaluateCombinationCondition(
-            mUnSlicedChildren, mLogicalOperation, nonSlicedConditionCache);
+        mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation,
+                                                              nonSlicedConditionCache);
 
         for (const int childIndex : mChildren) {
             // If any of the sliced condition in children condition changes, the combination
@@ -173,25 +170,6 @@
     }
 }
 
-ConditionState CombinationConditionTracker::getMetConditionDimension(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const std::vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
-    vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated);
-    // So far, this is fine as there is at most one child having sliced output.
-    for (const int childIndex : mChildren) {
-        conditionCache[childIndex] = conditionCache[childIndex] |
-            allConditions[childIndex]->getMetConditionDimension(
-                allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
-    }
-    evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
-    if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) {
-        dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY);
-    }
-    return conditionCache[mIndex];
-}
-
 bool CombinationConditionTracker::equalOutputDimensions(
         const std::vector<sp<ConditionTracker>>& allConditions,
         const vector<Matcher>& dimensions) const {
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 481cb20..39ff0ab 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -32,8 +32,8 @@
 
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-              std::vector<bool>& stack) override;
+              const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
+              std::vector<ConditionState>& initialConditionCache) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
@@ -43,17 +43,8 @@
 
     void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
-                        const vector<Matcher>& dimensionFields,
-                        const bool isSubOutputDimensionFields,
                         const bool isPartialLink,
-                        std::vector<ConditionState>& conditionCache,
-                        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
-
-    ConditionState getMetConditionDimension(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
-            std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
+                        std::vector<ConditionState>& conditionCache) const override;
 
     // Only one child predicate can have dimension.
     const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 5ff0e1d..62736c8 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -36,7 +36,7 @@
           mIndex(index),
           mInitialized(false),
           mTrackerIndex(),
-          mNonSlicedConditionState(ConditionState::kUnknown),
+          mUnSlicedPartCondition(ConditionState::kUnknown),
           mSliced(false){};
 
     virtual ~ConditionTracker(){};
@@ -51,10 +51,12 @@
     //                       need to call init() on children conditions)
     // conditionIdIndexMap: the mapping from condition id to its index.
     // stack: a bit map to keep track which nodes have been visited on the stack in the recursion.
+    // initialConditionCache: tracks initial conditions of all ConditionTrackers.
     virtual bool init(const std::vector<Predicate>& allConditionConfig,
                       const std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-                      std::vector<bool>& stack) = 0;
+                      std::vector<bool>& stack,
+                      std::vector<ConditionState>& initialConditionCache) = 0;
 
     // evaluate current condition given the new event.
     // event: the new log event
@@ -72,39 +74,19 @@
                                    std::vector<ConditionState>& conditionCache,
                                    std::vector<bool>& conditionChanged) = 0;
 
-    // Return the current condition state.
-    virtual ConditionState isConditionMet() const {
-        return mNonSlicedConditionState;
-    };
-
     // Query the condition with parameters.
     // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
     //                       condition.
     // [allConditions]: all condition trackers. This is needed because the condition evaluation is
     //                  done recursively
-    // [dimensionFields]: the needed dimension fields which should be all or subset of the condition
-    //                    tracker output dimension.
-    // [isSubOutputDimensionFields]: true if the needed dimension fields which is strictly subset of
-    //                               the condition tracker output dimension.
     // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields
     //                  in the condition tracker output dimension.
     // [conditionCache]: the cache holding the condition evaluation values.
-    // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination
-    //                    condition, it assumes that only one child predicate is sliced.
     virtual void isConditionMet(
             const ConditionKey& conditionParameters,
             const std::vector<sp<ConditionTracker>>& allConditions,
-            const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
             const bool isPartialLink,
-            std::vector<ConditionState>& conditionCache,
-            std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0;
-
-    virtual ConditionState getMetConditionDimension(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
-            std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0;
+            std::vector<ConditionState>& conditionCache) const = 0;
 
     // return the list of LogMatchingTracker index that this ConditionTracker uses.
     virtual const std::set<int>& getLogTrackerIndex() const {
@@ -140,8 +122,9 @@
         const std::vector<sp<ConditionTracker>>& allConditions,
         const vector<Matcher>& dimensions) const = 0;
 
+    // Return the current condition state of the unsliced part of the condition.
     inline ConditionState getUnSlicedPartConditionState() const  {
-        return mUnSlicedPart;
+        return mUnSlicedPartCondition;
     }
 
 protected:
@@ -156,10 +139,16 @@
     // the list of LogMatchingTracker index that this ConditionTracker uses.
     std::set<int> mTrackerIndex;
 
-    ConditionState mNonSlicedConditionState;
+    // This variable is only used for CombinationConditionTrackers.
+    // SimpleConditionTrackers technically don't have an unsliced part because
+    // they are either sliced or unsliced.
+    //
+    // CombinationConditionTrackers have multiple children ConditionTrackers
+    // that can be a mixture of sliced or unsliced. This tracks the
+    // condition of the unsliced part of the combination condition.
+    ConditionState mUnSlicedPartCondition;
 
     bool mSliced;
-    ConditionState mUnSlicedPart;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index f371316..c542032 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -22,27 +22,15 @@
 using std::vector;
 
 ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters,
-                                      const vector<Matcher>& dimensionFields,
-                                      const bool isSubOutputDimensionFields,
-                                      const bool isPartialLink,
-                                      std::unordered_set<HashableDimensionKey>* dimensionKeySet) {
+                                      const bool isPartialLink) {
     vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
 
     mAllConditions[index]->isConditionMet(
-        parameters, mAllConditions, dimensionFields, isSubOutputDimensionFields, isPartialLink,
-        cache, *dimensionKeySet);
+        parameters, mAllConditions, isPartialLink,
+        cache);
     return cache[index];
 }
 
-ConditionState ConditionWizard::getMetConditionDimension(
-        const int index, const vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
-        std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const {
-    return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields,
-                                                           isSubOutputDimensionFields,
-                                                           *dimensionsKeySet);
-}
-
 const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions(
         const int index) const {
     return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions);
@@ -79,4 +67,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 2c88147..8926479 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -40,15 +40,7 @@
     // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
     // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
     virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters,
-                                 const vector<Matcher>& dimensionFields,
-                                 const bool isSubOutputDimensionFields,
-                                 const bool isPartialLink,
-                                 std::unordered_set<HashableDimensionKey>* dimensionKeySet);
-
-    virtual ConditionState getMetConditionDimension(
-            const int index, const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
-            std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const;
+                                 const bool isPartialLink);
 
     virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const;
     virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 12ff184..efb4d49 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -85,12 +85,6 @@
         mInitialValue = ConditionState::kUnknown;
     }
 
-    mNonSlicedConditionState = mInitialValue;
-
-    if (!mSliced) {
-        mUnSlicedPart = mInitialValue;
-    }
-
     mInitialized = true;
 }
 
@@ -101,9 +95,11 @@
 bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
                                   const vector<sp<ConditionTracker>>& allConditionTrackers,
                                   const unordered_map<int64_t, int>& conditionIdIndexMap,
-                                  vector<bool>& stack) {
+                                  vector<bool>& stack,
+                                  vector<ConditionState>& initialConditionCache) {
     // SimpleConditionTracker does not have dependency on other conditions, thus we just return
     // if the initialization was successful.
+    initialConditionCache[mIndex] = mInitialValue;
     return mInitialized;
 }
 
@@ -141,9 +137,6 @@
     mInitialValue = ConditionState::kFalse;
     mSlicedConditionState.clear();
     conditionCache[mIndex] = ConditionState::kFalse;
-    if (!mSliced) {
-        mUnSlicedPart = ConditionState::kFalse;
-    }
 }
 
 bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -305,9 +298,7 @@
                 conditionCache[mIndex] =
                         itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
             }
-            mUnSlicedPart = conditionCache[mIndex];
         }
-
         return;
     }
 
@@ -333,18 +324,12 @@
     }
     conditionCache[mIndex] = overallState;
     conditionChangedCache[mIndex] = overallChanged;
-    if (!mSliced) {
-        mUnSlicedPart = overallState;
-    }
 }
 
 void SimpleConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
         const bool isPartialLink,
-        vector<ConditionState>& conditionCache,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
+        vector<ConditionState>& conditionCache) const {
 
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
@@ -356,18 +341,13 @@
 
     if (pair == conditionParameters.end()) {
         ConditionState conditionState = ConditionState::kNotEvaluated;
-        if (dimensionFields.size() > 0 && dimensionFields[0].mMatcher.getTag() == mDimensionTag) {
-            conditionState = conditionState | getMetConditionDimension(
-                allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
-        } else {
-            conditionState = conditionState | mInitialValue;
-            if (!mSliced) {
-                const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
-                if (itr != mSlicedConditionState.end()) {
-                    ConditionState sliceState =
-                        itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-                    conditionState = conditionState | sliceState;
-                }
+        conditionState = conditionState | mInitialValue;
+        if (!mSliced) {
+            const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
+            if (itr != mSlicedConditionState.end()) {
+                ConditionState sliceState =
+                    itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+                conditionState = conditionState | sliceState;
             }
         }
         conditionCache[mIndex] = conditionState;
@@ -385,15 +365,6 @@
                 slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
             if (slice.first.contains(key)) {
                 conditionState = conditionState | sliceState;
-                if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-                    if (isSubOutputDimensionFields) {
-                        HashableDimensionKey dimensionKey;
-                        filterValues(dimensionFields, slice.first.getValues(), &dimensionKey);
-                        dimensionsKeySet.insert(dimensionKey);
-                    } else {
-                        dimensionsKeySet.insert(slice.first);
-                    }
-                }
             }
         }
     } else {
@@ -403,15 +374,6 @@
             ConditionState sliceState =
                 startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
             conditionState = conditionState | sliceState;
-            if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-                if (isSubOutputDimensionFields) {
-                    HashableDimensionKey dimensionKey;
-                    filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKey);
-                    dimensionsKeySet.insert(dimensionKey);
-                } else {
-                    dimensionsKeySet.insert(startedCountIt->first);
-                }
-            }
         }
 
     }
@@ -419,41 +381,6 @@
     VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
 }
 
-ConditionState SimpleConditionTracker::getMetConditionDimension(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
-    ConditionState conditionState = mInitialValue;
-    if (dimensionFields.size() == 0 || mOutputDimensions.size() == 0 ||
-        dimensionFields[0].mMatcher.getTag() != mOutputDimensions[0].mMatcher.getTag()) {
-        const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
-        if (itr != mSlicedConditionState.end()) {
-            ConditionState sliceState =
-                itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-            conditionState = conditionState | sliceState;
-        }
-        return conditionState;
-    }
-
-    for (const auto& slice : mSlicedConditionState) {
-        ConditionState sliceState =
-            slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionState = conditionState | sliceState;
-
-        if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-            if (isSubOutputDimensionFields) {
-                HashableDimensionKey dimensionKey;
-                filterValues(dimensionFields, slice.first.getValues(), &dimensionKey);
-                dimensionsKeySet.insert(dimensionKey);
-            } else {
-                dimensionsKeySet.insert(slice.first);
-            }
-        }
-    }
-    return conditionState;
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 47d1ece..ea7f87b 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -37,8 +37,8 @@
 
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-              std::vector<bool>& stack) override;
+              const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
+              std::vector<ConditionState>& initialConditionCache) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
@@ -48,17 +48,8 @@
 
     void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
-                        const vector<Matcher>& dimensionFields,
-                        const bool isSubOutputDimensionFields,
                         const bool isPartialLink,
-                        std::vector<ConditionState>& conditionCache,
-                        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
-
-    ConditionState getMetConditionDimension(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
-            std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
+                        std::vector<ConditionState>& conditionCache) const override;
 
     virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
             const std::vector<sp<ConditionTracker>>& allConditions) const {
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp
deleted file mode 100644
index 1965ce6..0000000
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "StateTracker.h"
-#include "guardrail/StatsdStats.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::string;
-using std::unordered_set;
-using std::vector;
-
-StateTracker::StateTracker(const ConfigKey& key, const int64_t& id, const int index,
-                           const SimplePredicate& simplePredicate,
-                           const unordered_map<int64_t, int>& trackerNameIndexMap,
-                           const vector<Matcher> primaryKeys)
-    : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) {
-    if (simplePredicate.has_start()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.start());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
-            return;
-        }
-        mStartLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStartLogMatcherIndex);
-    } else {
-        ALOGW("Condition %lld must have a start matcher", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.has_dimensions()) {
-        translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
-        if (mOutputDimensions.size() > 0) {
-            mSliced = true;
-            mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
-        } else {
-            ALOGW("Condition %lld has invalid dimensions", (long long)id);
-            return;
-        }
-    } else {
-        ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
-        mInitialValue = ConditionState::kFalse;
-    } else {
-        mInitialValue = ConditionState::kUnknown;
-    }
-
-    mNonSlicedConditionState = mInitialValue;
-    mInitialized = true;
-}
-
-StateTracker::~StateTracker() {
-    VLOG("~StateTracker()");
-}
-
-bool StateTracker::init(const vector<Predicate>& allConditionConfig,
-                        const vector<sp<ConditionTracker>>& allConditionTrackers,
-                        const unordered_map<int64_t, int>& conditionIdIndexMap,
-                        vector<bool>& stack) {
-    return mInitialized;
-}
-
-void StateTracker::dumpState() {
-    VLOG("StateTracker %lld DUMP:", (long long)mConditionId);
-    for (const auto& value : mSlicedState) {
-        VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
-    }
-    VLOG("Last Changed to True: ");
-    for (const auto& value : mLastChangedToTrueDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-    VLOG("Last Changed to False: ");
-    for (const auto& value : mLastChangedToFalseDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-}
-
-bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) {
-    if (mSlicedState.find(newKey) != mSlicedState.end()) {
-        // if the condition is not sliced or the key is not new, we are good!
-        return false;
-    }
-    // 1. Report the tuple count if the tuple count > soft limit
-    if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-        size_t newTupleCount = mSlicedState.size() + 1;
-        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
-        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("Predicate %lld dropping data for dimension key %s",
-                (long long)mConditionId, newKey.toString().c_str());
-            return true;
-        }
-    }
-    return false;
-}
-
-void StateTracker::evaluateCondition(const LogEvent& event,
-                                     const vector<MatchingState>& eventMatcherValues,
-                                     const vector<sp<ConditionTracker>>& mAllConditions,
-                                     vector<ConditionState>& conditionCache,
-                                     vector<bool>& conditionChangedCache) {
-    mLastChangedToTrueDimensions.clear();
-    mLastChangedToFalseDimensions.clear();
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    if (mStartLogMatcherIndex >= 0 &&
-        eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) {
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-
-    VLOG("StateTracker evaluate event %s", event.ToString().c_str());
-
-    // Primary key can exclusive fields must be simple fields. so there won't be more than
-    // one keys matched.
-    HashableDimensionKey primaryKey;
-    HashableDimensionKey state;
-    if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) ||
-        !filterValues(mOutputDimensions, event.getValues(), &state)) {
-        ALOGE("Failed to filter fields in the event?? panic now!");
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-    hitGuardRail(primaryKey);
-
-    VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
-
-    auto it = mSlicedState.find(primaryKey);
-    if (it == mSlicedState.end()) {
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        mLastChangedToTrueDimensions.insert(state);
-        conditionChangedCache[mIndex] = true;
-    } else if (!(it->second == state)) {
-        mLastChangedToFalseDimensions.insert(it->second);
-        mLastChangedToTrueDimensions.insert(state);
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = true;
-    } else {
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = false;
-    }
-
-    if (DEBUG) {
-        dumpState();
-    }
-    return;
-}
-
-void StateTracker::isConditionMet(
-        const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
-        const bool isPartialLink,
-        vector<ConditionState>& conditionCache,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    const auto pair = conditionParameters.find(mConditionId);
-    if (pair == conditionParameters.end()) {
-        if (mSlicedState.size() > 0) {
-            conditionCache[mIndex] = ConditionState::kTrue;
-
-            for (const auto& state : mSlicedState) {
-                dimensionsKeySet.insert(state.second);
-            }
-        } else {
-            conditionCache[mIndex] = ConditionState::kUnknown;
-        }
-        return;
-    }
-
-    const auto& primaryKey = pair->second;
-    conditionCache[mIndex] = mInitialValue;
-    auto it = mSlicedState.find(primaryKey);
-    if (it != mSlicedState.end()) {
-        conditionCache[mIndex] = ConditionState::kTrue;
-        dimensionsKeySet.insert(it->second);
-    }
-}
-
-ConditionState StateTracker::getMetConditionDimension(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensionFields,
-        const bool isSubOutputDimensionFields,
-        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
-    if (mSlicedState.size() > 0) {
-        for (const auto& state : mSlicedState) {
-            dimensionsKeySet.insert(state.second);
-        }
-        return ConditionState::kTrue;
-    }
-
-    return mInitialValue;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h
deleted file mode 100644
index 2bdf98c..0000000
--- a/cmds/statsd/src/condition/StateTracker.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "ConditionTracker.h"
-#include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StateTracker : public virtual ConditionTracker {
-public:
-    StateTracker(const ConfigKey& key, const int64_t& id, const int index,
-                 const SimplePredicate& simplePredicate,
-                 const std::unordered_map<int64_t, int>& trackerNameIndexMap,
-                 const vector<Matcher> primaryKeys);
-
-    ~StateTracker();
-
-    bool init(const std::vector<Predicate>& allConditionConfig,
-              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-              std::vector<bool>& stack) override;
-
-    void evaluateCondition(const LogEvent& event,
-                           const std::vector<MatchingState>& eventMatcherValues,
-                           const std::vector<sp<ConditionTracker>>& mAllConditions,
-                           std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
-
-    /**
-     * Note: dimensionFields will be ignored in StateTracker, because we demand metrics
-     * must take the entire dimension fields from StateTracker. This is to make implementation
-     * simple and efficient.
-     *
-     * For example: wakelock duration by uid process states:
-     *              dimension in condition must be {uid, process state}.
-     */
-    void isConditionMet(const ConditionKey& conditionParameters,
-                        const std::vector<sp<ConditionTracker>>& allConditions,
-                        const vector<Matcher>& dimensionFields,
-                        const bool isSubOutputDimensionFields,
-                        const bool isPartialLink,
-                        std::vector<ConditionState>& conditionCache,
-                        std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
-
-    /**
-     * Note: dimensionFields will be ignored in StateTracker, because we demand metrics
-     * must take the entire dimension fields from StateTracker. This is to make implementation
-     * simple and efficient.
-     */
-    ConditionState getMetConditionDimension(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            const vector<Matcher>& dimensionFields,
-            const bool isSubOutputDimensionFields,
-            std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
-
-    virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToTrueDimensions;
-    }
-
-    virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToFalseDimensions;
-    }
-
-    bool IsChangedDimensionTrackable() const  override { return true; }
-
-    bool IsSimpleCondition() const  override { return true; }
-
-    bool equalOutputDimensions(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensions) const override {
-            return equalDimensions(mOutputDimensions, dimensions);
-    }
-
-    void getTrueSlicedDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            std::set<HashableDimensionKey>* dimensions) const override {
-        for (const auto& itr : mSlicedState) {
-            dimensions->insert(itr.second);
-        }
-    }
-
-private:
-    const ConfigKey mConfigKey;
-
-    // The index of the LogEventMatcher which defines the start.
-    int mStartLogMatcherIndex;
-
-    std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
-    std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
-
-    std::vector<Matcher> mOutputDimensions;
-    std::vector<Matcher> mPrimaryKeys;
-
-    ConditionState mInitialValue;
-
-    int mDimensionTag;
-
-    void dumpState();
-
-    bool hitGuardRail(const HashableDimensionKey& newKey);
-
-    // maps from [primary_key] to [primary_key, exclusive_state].
-    std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
-
-    FRIEND_TEST(StateTrackerTest, TestStateChange);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 13d251a..13020e0 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -42,7 +42,78 @@
 using android::base::StringPrintf;
 using std::unique_ptr;
 
-ConfigManager::ConfigManager() {
+struct ConfigReceiverDeathCookie {
+    ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey,
+                              const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mConfigKey(configKey), mPir(pir) {
+    }
+
+    wp<ConfigManager> mConfigManager;
+    ConfigKey mConfigKey;
+    shared_ptr<IPendingIntentRef> mPir;
+};
+
+void ConfigManager::configReceiverDied(void* cookie) {
+    auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
+    ConfigKey& configKey = cookie_->mConfigKey;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
+
+    // Erase the mapping from the config key to the config receiver (pir) if the
+    // mapping still exists.
+    lock_guard<mutex> lock(thiz->mMutex);
+    auto it = thiz->mConfigReceivers.find(configKey);
+    if (it != thiz->mConfigReceivers.end() && it->second == pir) {
+        thiz->mConfigReceivers.erase(configKey);
+    }
+
+    // The death recipient corresponding to this specific pir can never be
+    // triggered again, so free up resources.
+    delete cookie_;
+}
+
+struct ActiveConfigChangedReceiverDeathCookie {
+    ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid,
+                                           const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mUid(uid), mPir(pir) {
+    }
+
+    wp<ConfigManager> mConfigManager;
+    int mUid;
+    shared_ptr<IPendingIntentRef> mPir;
+};
+
+void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
+    auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
+    int uid = cookie_->mUid;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
+
+    // Erase the mapping from the config key to the active config changed
+    // receiver (pir) if the mapping still exists.
+    lock_guard<mutex> lock(thiz->mMutex);
+    auto it = thiz->mActiveConfigsChangedReceivers.find(uid);
+    if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) {
+        thiz->mActiveConfigsChangedReceivers.erase(uid);
+    }
+
+    // The death recipient corresponding to this specific pir can never
+    // be triggered again, so free up resources.
+    delete cookie_;
+}
+
+ConfigManager::ConfigManager() :
+    mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)),
+    mActiveConfigChangedReceiverDeathRecipient(
+            AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
 }
 
 ConfigManager::~ConfigManager() {
@@ -114,9 +185,12 @@
     }
 }
 
-void ConfigManager::SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender) {
+void ConfigManager::SetConfigReceiver(const ConfigKey& key,
+                                      const shared_ptr<IPendingIntentRef>& pir) {
     lock_guard<mutex> lock(mMutex);
-    mConfigReceivers[key] = intentSender;
+    mConfigReceivers[key] = pir;
+    AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(),
+                         new ConfigReceiverDeathCookie(this, key, pir));
 }
 
 void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
@@ -125,9 +199,13 @@
 }
 
 void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
-                                                    const sp<IBinder>& intentSender) {
-    lock_guard<mutex> lock(mMutex);
-    mActiveConfigsChangedReceivers[uid] = intentSender;
+                                                    const shared_ptr<IPendingIntentRef>& pir) {
+    {
+        lock_guard<mutex> lock(mMutex);
+        mActiveConfigsChangedReceivers[uid] = pir;
+    }
+    AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(),
+                         new ActiveConfigChangedReceiverDeathCookie(this, uid, pir));
 }
 
 void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
@@ -146,25 +224,11 @@
             // Remove from map
             uidIt->second.erase(key);
 
-            // No more configs for this uid, lets remove the active configs callback.
-            if (uidIt->second.empty()) {
-                auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-                    if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-                        mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
-                    }
-            }
-
             for (const sp<ConfigListener>& listener : mListeners) {
                 broadcastList.push_back(listener);
             }
         }
 
-        auto itReceiver = mConfigReceivers.find(key);
-        if (itReceiver != mConfigReceivers.end()) {
-            // Remove from map
-            mConfigReceivers.erase(itReceiver);
-        }
-
         // Remove from disk. There can still be a lingering file on disk so we check
         // whether or not the config was on memory.
         remove_saved_configs(key);
@@ -195,12 +259,6 @@
             // Remove from map
                 remove_saved_configs(*it);
                 removed.push_back(*it);
-                mConfigReceivers.erase(*it);
-        }
-
-        auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-        if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-            mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
         }
 
         mConfigs.erase(uidIt);
@@ -234,8 +292,6 @@
             uidIt = mConfigs.erase(uidIt);
         }
 
-        mConfigReceivers.clear();
-        mActiveConfigsChangedReceivers.clear();
         for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
@@ -262,7 +318,7 @@
     return ret;
 }
 
-const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
     lock_guard<mutex> lock(mMutex);
 
     auto it = mConfigReceivers.find(key);
@@ -273,7 +329,8 @@
     }
 }
 
-const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid)
+        const {
     lock_guard<mutex> lock(mMutex);
 
     auto it = mActiveConfigsChangedReceivers.find(uid);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 1fdec31..bef057f 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -16,16 +16,18 @@
 
 #pragma once
 
-#include "binder/IBinder.h"
 #include "config/ConfigKey.h"
 #include "config/ConfigListener.h"
 
-#include <map>
+#include <aidl/android/os/IPendingIntentRef.h>
 #include <mutex>
 #include <string>
 
 #include <stdio.h>
 
+using aidl::android::os::IPendingIntentRef;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -63,12 +65,12 @@
     /**
      * Sets the broadcast receiver for a configuration key.
      */
-    void SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender);
+    void SetConfigReceiver(const ConfigKey& key, const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Returns the package name and class name representing the broadcast receiver for this config.
      */
-    const sp<android::IBinder> GetConfigReceiver(const ConfigKey& key) const;
+    const shared_ptr<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
 
     /**
      * Returns all config keys registered.
@@ -84,13 +86,13 @@
      * Sets the broadcast receiver that is notified whenever the list of active configs
      * changes for this uid.
      */
-    void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+    void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Returns the broadcast receiver for active configs changed for this uid.
      */
 
-    const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+    const shared_ptr<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
 
     /**
      * Erase any active configs changed broadcast receiver associated with this uid.
@@ -140,21 +142,38 @@
     std::map<int, std::set<ConfigKey>> mConfigs;
 
     /**
-     * Each config key can be subscribed by up to one receiver, specified as IBinder from
-     * PendingIntent.
+     * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef.
      */
-    std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
+    std::map<ConfigKey, shared_ptr<IPendingIntentRef>> mConfigReceivers;
 
     /**
      * Each uid can be subscribed by up to one receiver to notify that the list of active configs
-     * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+     * for this uid has changed. The receiver is specified as IPendingIntentRef.
      */
-     std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+     std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
 
     /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
+
+    // Death recipients that are triggered when the host process holding an
+    // IPendingIntentRef dies.
+    ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient;
+    ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a config receiver dies.
+     * The cookie is a pointer to a ConfigReceiverDeathCookie.
+     */
+    static void configReceiverDied(void* cookie);
+
+    /**
+     * Death recipient callback that is called when an active config changed
+     * receiver dies. The cookie is a pointer to an
+     * ActiveConfigChangedReceiverDeathCookie.
+     */
+    static void activeConfigChangedReceiverDied(void* cookie);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/experiment_ids.proto b/cmds/statsd/src/experiment_ids.proto
new file mode 100644
index 0000000..c203631
--- /dev/null
+++ b/cmds/statsd/src/experiment_ids.proto
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "ExperimentIdsProto";
+
+// StatsLogProcessor uses the proto to parse experiment ids from
+// BinaryPushStateChanged atoms. This needs to be in sync with
+// TrainExperimentIds in atoms.proto.
+message ExperimentIds {
+    repeated int64 experiment_id = 1;
+}
diff --git a/cmds/statsd/src/external/CarStatsPuller.cpp b/cmds/statsd/src/external/CarStatsPuller.cpp
deleted file mode 100644
index 70c0456..0000000
--- a/cmds/statsd/src/external/CarStatsPuller.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 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 false
-#include "Log.h"
-
-#include <binder/IServiceManager.h>
-#include <com/android/internal/car/ICarStatsService.h>
-
-#include "CarStatsPuller.h"
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-
-using android::binder::Status;
-using com::android::internal::car::ICarStatsService;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-static std::mutex gCarStatsMutex;
-static sp<ICarStatsService> gCarStats = nullptr;
-
-class CarStatsDeathRecipient : public android::IBinder::DeathRecipient {
- public:
-     CarStatsDeathRecipient() = default;
-     ~CarStatsDeathRecipient() override = default;
-
-  // android::IBinder::DeathRecipient override:
-  void binderDied(const android::wp<android::IBinder>& /* who */) override {
-      ALOGE("Car service has died");
-      std::lock_guard<std::mutex> lock(gCarStatsMutex);
-      if (gCarStats) {
-          sp<IBinder> binder = IInterface::asBinder(gCarStats);
-          binder->unlinkToDeath(this);
-          gCarStats = nullptr;
-      }
-  }
-};
-
-static sp<CarStatsDeathRecipient> gDeathRecipient = new CarStatsDeathRecipient();
-
-static sp<ICarStatsService> getCarService() {
-    std::lock_guard<std::mutex> lock(gCarStatsMutex);
-    if (!gCarStats) {
-        const sp<IBinder> binder = defaultServiceManager()->checkService(String16("car_stats"));
-        if (!binder) {
-            ALOGW("Car service is unavailable");
-            return nullptr;
-        }
-        gCarStats = interface_cast<ICarStatsService>(binder);
-        binder->linkToDeath(gDeathRecipient);
-    }
-    return gCarStats;
-}
-
-CarStatsPuller::CarStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-bool CarStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    const sp<ICarStatsService> carService = getCarService();
-    if (!carService) {
-        return false;
-    }
-
-    vector<StatsLogEventWrapper> returned_value;
-    Status status = carService->pullData(mTagId, &returned_value);
-    if (!status.isOk()) {
-        ALOGW("CarStatsPuller::pull failed for %d", mTagId);
-        return false;
-    }
-
-    data->clear();
-    for (const StatsLogEventWrapper& it : returned_value) {
-        LogEvent::createLogEvents(it, *data);
-    }
-    VLOG("CarStatsPuller::pull succeeded for %d", mTagId);
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp
deleted file mode 100644
index 0d3aca0..0000000
--- a/cmds/statsd/src/external/GpuStatsPuller.cpp
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 2019 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 "GpuStatsPuller.h"
-
-#include <binder/IServiceManager.h>
-#include <graphicsenv/GpuStatsInfo.h>
-#include <graphicsenv/IGpuService.h>
-
-#include "logd/LogEvent.h"
-
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using android::util::ProtoReader;
-
-GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-static sp<IGpuService> getGpuService() {
-    const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu"));
-    if (!binder) {
-        ALOGE("Failed to get gpu service");
-        return nullptr;
-    }
-
-    return interface_cast<IGpuService>(binder);
-}
-
-static bool pullGpuStatsGlobalInfo(const sp<IGpuService>& gpuService,
-                                   std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::vector<GpuStatsGlobalInfo> stats;
-    status_t status = gpuService->getGpuStatsGlobalInfo(&stats);
-    if (status != OK) {
-        return false;
-    }
-
-    data->clear();
-    data->reserve(stats.size());
-    for (const auto& info : stats) {
-        std::shared_ptr<LogEvent> event = make_shared<LogEvent>(
-                android::util::GPU_STATS_GLOBAL_INFO, getWallClockNs(), getElapsedRealtimeNs());
-        if (!event->write(info.driverPackageName)) return false;
-        if (!event->write(info.driverVersionName)) return false;
-        if (!event->write((int64_t)info.driverVersionCode)) return false;
-        if (!event->write(info.driverBuildTime)) return false;
-        if (!event->write((int64_t)info.glLoadingCount)) return false;
-        if (!event->write((int64_t)info.glLoadingFailureCount)) return false;
-        if (!event->write((int64_t)info.vkLoadingCount)) return false;
-        if (!event->write((int64_t)info.vkLoadingFailureCount)) return false;
-        if (!event->write(info.vulkanVersion)) return false;
-        if (!event->write(info.cpuVulkanVersion)) return false;
-        if (!event->write(info.glesVersion)) return false;
-        if (!event->write((int64_t)info.angleLoadingCount)) return false;
-        if (!event->write((int64_t)info.angleLoadingFailureCount)) return false;
-        event->init();
-        data->emplace_back(event);
-    }
-
-    return true;
-}
-
-static bool pullGpuStatsAppInfo(const sp<IGpuService>& gpuService,
-                                std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::vector<GpuStatsAppInfo> stats;
-    status_t status = gpuService->getGpuStatsAppInfo(&stats);
-    if (status != OK) {
-        return false;
-    }
-
-    data->clear();
-    data->reserve(stats.size());
-    for (const auto& info : stats) {
-        std::shared_ptr<LogEvent> event = make_shared<LogEvent>(
-                android::util::GPU_STATS_APP_INFO, getWallClockNs(), getElapsedRealtimeNs());
-        if (!event->write(info.appPackageName)) return false;
-        if (!event->write((int64_t)info.driverVersionCode)) return false;
-        if (!event->write(int64VectorToProtoByteString(info.glDriverLoadingTime))) return false;
-        if (!event->write(int64VectorToProtoByteString(info.vkDriverLoadingTime))) return false;
-        if (!event->write(int64VectorToProtoByteString(info.angleDriverLoadingTime))) return false;
-        if (!event->write(info.cpuVulkanInUse)) return false;
-        event->init();
-        data->emplace_back(event);
-    }
-
-    return true;
-}
-
-bool GpuStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    const sp<IGpuService> gpuService = getGpuService();
-    if (!gpuService) {
-        return false;
-    }
-
-    switch (mTagId) {
-        case android::util::GPU_STATS_GLOBAL_INFO:
-            return pullGpuStatsGlobalInfo(gpuService, data);
-        case android::util::GPU_STATS_APP_INFO:
-            return pullGpuStatsAppInfo(gpuService, data);
-        default:
-            break;
-    }
-
-    return false;
-}
-
-static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) {
-    if (!proto.size()) return "";
-
-    std::string byteString;
-    sp<ProtoReader> reader = proto.data();
-    while (reader->readBuffer() != nullptr) {
-        const size_t toRead = reader->currentToRead();
-        byteString.append((char*)reader->readBuffer(), toRead);
-        reader->move(toRead);
-    }
-
-    if (byteString.size() != proto.size()) return "";
-
-    return byteString;
-}
-
-std::string int64VectorToProtoByteString(const std::vector<int64_t>& value) {
-    if (value.empty()) return "";
-
-    ProtoOutputStream proto;
-    for (const auto& ele : value) {
-        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
-                            1 /* field id */,
-                    (long long)ele);
-    }
-
-    return protoOutputStreamToByteString(proto);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/GpuStatsPuller.h b/cmds/statsd/src/external/GpuStatsPuller.h
deleted file mode 100644
index 2da199c..0000000
--- a/cmds/statsd/src/external/GpuStatsPuller.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull GpuStats from GpuService.
- */
-class GpuStatsPuller : public StatsPuller {
-public:
-    explicit GpuStatsPuller(const int tagId);
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-// convert a int64_t vector into a byte string for proto message like:
-// message RepeatedInt64Wrapper {
-//   repeated int64 value = 1;
-// }
-std::string int64VectorToProtoByteString(const std::vector<int64_t>& value);
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/Perfetto.h b/cmds/statsd/src/external/Perfetto.h
index ab2c195..095782a 100644
--- a/cmds/statsd/src/external/Perfetto.h
+++ b/cmds/statsd/src/external/Perfetto.h
@@ -16,10 +16,6 @@
 
 #pragma once
 
-#include <android/os/StatsLogEventWrapper.h>
-
-using android::os::StatsLogEventWrapper;
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp
deleted file mode 100644
index dc69b78..0000000
--- a/cmds/statsd/src/external/PowerStatsPuller.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/power/stats/1.0/IPowerStats.h>
-
-#include <vector>
-
-#include "PowerStatsPuller.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-using android::hardware::hidl_vec;
-using android::hardware::power::stats::V1_0::IPowerStats;
-using android::hardware::power::stats::V1_0::EnergyData;
-using android::hardware::power::stats::V1_0::RailInfo;
-using android::hardware::power::stats::V1_0::Status;
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHal = nullptr;
-static std::mutex gPowerStatsHalMutex;
-static bool gPowerStatsExist = true; // Initialized to ensure making a first attempt.
-static std::vector<RailInfo> gRailInfo;
-
-struct PowerStatsPullerDeathRecipient : virtual public hardware::hidl_death_recipient {
-    virtual void serviceDied(uint64_t cookie,
-            const wp<android::hidl::base::V1_0::IBase>& who) override {
-        // The HAL just died. Reset all handles to HAL services.
-        std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-        gPowerStatsHal = nullptr;
-    }
-};
-
-static sp<PowerStatsPullerDeathRecipient> gDeathRecipient = new PowerStatsPullerDeathRecipient();
-
-static bool getPowerStatsHalLocked() {
-    if (gPowerStatsHal == nullptr && gPowerStatsExist) {
-        gPowerStatsHal = android::hardware::power::stats::V1_0::IPowerStats::getService();
-        if (gPowerStatsHal == nullptr) {
-            ALOGW("Couldn't load power.stats HAL service");
-            gPowerStatsExist = false;
-        } else {
-            // Link death recipient to power.stats service handle
-            hardware::Return<bool> linked = gPowerStatsHal->linkToDeath(gDeathRecipient, 0);
-            if (!linked.isOk()) {
-                ALOGE("Transaction error in linking to power.stats HAL death: %s",
-                        linked.description().c_str());
-                gPowerStatsHal = nullptr;
-                return false;
-            } else if (!linked) {
-                ALOGW("Unable to link to power.stats HAL death notifications");
-                // We should still continue even though linking failed
-            }
-        }
-    }
-    return gPowerStatsHal != nullptr;
-}
-
-PowerStatsPuller::PowerStatsPuller() : StatsPuller(android::util::ON_DEVICE_POWER_MEASUREMENT) {
-}
-
-bool PowerStatsPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
-    if (!getPowerStatsHalLocked()) {
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
-    data->clear();
-
-    // Pull getRailInfo if necessary
-    if (gRailInfo.empty()) {
-        bool resultSuccess = true;
-        Return<void> ret = gPowerStatsHal->getRailInfo(
-                [&resultSuccess](const hidl_vec<RailInfo> &list, Status status) {
-                    resultSuccess = (status == Status::SUCCESS || status == Status::NOT_SUPPORTED);
-                    if (status != Status::SUCCESS) return;
-
-                    gRailInfo.reserve(list.size());
-                    for (size_t i = 0; i < list.size(); ++i) {
-                        gRailInfo.push_back(list[i]);
-                    }
-                });
-        if (!resultSuccess || !ret.isOk()) {
-            ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str());
-            gPowerStatsHal = nullptr;
-            return false;
-        }
-        // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again.
-        if (gRailInfo.empty()) {
-            ALOGE("power.stats has no rail information");
-            gPowerStatsExist = false; // No rail info, so never try again.
-            gPowerStatsHal = nullptr;
-            return false;
-        }
-    }
-
-    // Pull getEnergyData and write the data out
-    const hidl_vec<uint32_t> desiredRailIndices; // Empty vector indicates we want all.
-    bool resultSuccess = true;
-    Return<void> ret = gPowerStatsHal->getEnergyData(desiredRailIndices,
-                [&data, wallClockTimestampNs, elapsedTimestampNs, &resultSuccess]
-                (hidl_vec<EnergyData> energyDataList, Status status) {
-                    resultSuccess = (status == Status::SUCCESS);
-                    if (!resultSuccess) return;
-
-                    for (size_t i = 0; i < energyDataList.size(); i++) {
-                        const EnergyData& energyData = energyDataList[i];
-
-                        if (energyData.index >= gRailInfo.size()) {
-                            ALOGE("power.stats getEnergyData() returned an invalid rail index %u.",
-                                    energyData.index);
-                            resultSuccess = false;
-                            return;
-                        }
-                        const RailInfo& rail = gRailInfo[energyData.index];
-
-                        auto ptr = make_shared<LogEvent>(android::util::ON_DEVICE_POWER_MEASUREMENT,
-                              wallClockTimestampNs, elapsedTimestampNs);
-                        ptr->write(rail.subsysName);
-                        ptr->write(rail.railName);
-                        ptr->write(energyData.timestamp);
-                        ptr->write(energyData.energy);
-                        ptr->init();
-                        data->push_back(ptr);
-
-                        VLOG("power.stat: %s.%s: %llu, %llu",
-                             rail.subsysName.c_str(),
-                             rail.railName.c_str(),
-                             (unsigned long long)energyData.timestamp,
-                             (unsigned long long)energyData.energy);
-                    }
-                });
-    if (!resultSuccess || !ret.isOk()) {
-        ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str());
-        gPowerStatsHal = nullptr;
-        return false;
-    }
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/PowerStatsPuller.h b/cmds/statsd/src/external/PowerStatsPuller.h
deleted file mode 100644
index 6f15bd6..0000000
--- a/cmds/statsd/src/external/PowerStatsPuller.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads hal for power.stats
- */
-class PowerStatsPuller : public StatsPuller {
-public:
-    PowerStatsPuller();
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/PullResultReceiver.cpp b/cmds/statsd/src/external/PullResultReceiver.cpp
new file mode 100644
index 0000000..8aa4792
--- /dev/null
+++ b/cmds/statsd/src/external/PullResultReceiver.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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 "PullResultReceiver.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+PullResultReceiver::PullResultReceiver(
+        std::function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCb)
+    : pullFinishCallback(std::move(pullFinishCb)) {
+}
+
+Status PullResultReceiver::pullFinished(int32_t atomTag, bool success,
+                                        const vector<StatsEventParcel>& output) {
+    pullFinishCallback(atomTag, success, output);
+    return Status::ok();
+}
+
+PullResultReceiver::~PullResultReceiver() {
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/PullResultReceiver.h b/cmds/statsd/src/external/PullResultReceiver.h
new file mode 100644
index 0000000..ceaae80
--- /dev/null
+++ b/cmds/statsd/src/external/PullResultReceiver.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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 <aidl/android/os/BnPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
+
+using namespace std;
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class PullResultReceiver : public BnPullAtomResultReceiver {
+public:
+    PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)>
+                               pullFinishCallback);
+    ~PullResultReceiver();
+
+    /**
+     * Binder call for finishing a pull.
+     */
+    Status pullFinished(int32_t atomTag, bool success,
+                        const vector<StatsEventParcel>& output) override;
+
+private:
+    function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/CarStatsPuller.h b/cmds/statsd/src/external/PullUidProvider.h
similarity index 69%
rename from cmds/statsd/src/external/CarStatsPuller.h
rename to cmds/statsd/src/external/PullUidProvider.h
index ca0f1a9..2318c50 100644
--- a/cmds/statsd/src/external/CarStatsPuller.h
+++ b/cmds/statsd/src/external/PullUidProvider.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -13,22 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #pragma once
 
+#include <utils/RefBase.h>
+
 #include "StatsPuller.h"
+#include "logd/LogEvent.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-/**
- * Pull atoms from CarService.
- */
-class CarStatsPuller : public StatsPuller {
+class PullUidProvider : virtual public RefBase {
 public:
-    explicit CarStatsPuller(const int tagId);
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+    virtual ~PullUidProvider() {}
+
+    /**
+     * @param atomId The atom for which to get the uids.
+     */
+    virtual vector<int32_t> getPullAtomUids(int32_t atomId) = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
deleted file mode 100644
index 75b63f4..0000000
--- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/health/2.0/IHealth.h>
-#include <healthhalutils/HealthHalUtils.h>
-#include "external/ResourceHealthManagerPuller.h"
-#include "external/StatsPuller.h"
-
-#include "ResourceHealthManagerPuller.h"
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-#include "statslog.h"
-
-using android::hardware::hidl_vec;
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::health::V2_0::get_health_service;
-using android::hardware::health::V2_0::HealthInfo;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-sp<android::hardware::health::V2_0::IHealth> gHealthHal = nullptr;
-
-bool getHealthHal() {
-    if (gHealthHal == nullptr) {
-        gHealthHal = get_health_service();
-    }
-    return gHealthHal != nullptr;
-}
-
-ResourceHealthManagerPuller::ResourceHealthManagerPuller(int tagId) : StatsPuller(tagId) {
-}
-
-// TODO(b/110565992): add other health atoms (eg. Temperature).
-bool ResourceHealthManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    if (!getHealthHal()) {
-        ALOGE("Health Hal not loaded");
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
-    data->clear();
-    bool result_success = true;
-
-    // Get the data from the Health HAL (hardware/interfaces/health/1.0/types.hal).
-    Return<void> ret = gHealthHal->getHealthInfo([&](Result r, HealthInfo v) {
-        if (r != Result::SUCCESS) {
-            result_success = false;
-            return;
-        }
-        if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) {
-            auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY,
-                                             wallClockTimestampNs, elapsedTimestampNs);
-            ptr->write(v.legacy.batteryChargeCounter);
-            ptr->init();
-            data->push_back(ptr);
-        } else if (mTagId == android::util::FULL_BATTERY_CAPACITY) {
-            auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY,
-                                             wallClockTimestampNs, elapsedTimestampNs);
-            ptr->write(v.legacy.batteryFullCharge);
-            ptr->init();
-            data->push_back(ptr);
-        } else if (mTagId == android::util::BATTERY_VOLTAGE) {
-            auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs,
-                                             elapsedTimestampNs);
-            ptr->write(v.legacy.batteryVoltage);
-            ptr->init();
-            data->push_back(ptr);
-        } else if (mTagId == android::util::BATTERY_LEVEL) {
-            auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs,
-                                             elapsedTimestampNs);
-            ptr->write(v.legacy.batteryLevel);
-            ptr->init();
-            data->push_back(ptr);
-        } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) {
-            auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT,
-                                             wallClockTimestampNs, elapsedTimestampNs);
-            ptr->write(v.legacy.batteryCycleCount);
-            ptr->init();
-            data->push_back(ptr);
-        } else {
-            ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId);
-        }
-    });
-    if (!result_success || !ret.isOk()) {
-        ALOGE("getHealthHal() failed: health HAL service not available. Description: %s",
-              ret.description().c_str());
-        if (!ret.isOk() && ret.isDeadObject()) {
-            gHealthHal = nullptr;
-        }
-        return false;
-    }
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.h b/cmds/statsd/src/external/ResourceHealthManagerPuller.h
deleted file mode 100644
index f650fcc..0000000
--- a/cmds/statsd/src/external/ResourceHealthManagerPuller.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads Ihealth.hal
- */
-class ResourceHealthManagerPuller : public StatsPuller {
-public:
-    explicit ResourceHealthManagerPuller(int tagId);
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index d718273..78e6f09 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -17,43 +17,98 @@
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
-#include <android/os/IStatsPullerCallback.h>
-
 #include "StatsCallbackPuller.h"
+#include "PullResultReceiver.h"
+#include "StatsPullerManager.h"
 #include "logd/LogEvent.h"
 #include "stats_log_util.h"
 
-using namespace android::binder;
+#include <aidl/android/util/StatsEventParcel.h>
+
+using namespace std;
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IStatsPullerCallback>& callback) :
-        StatsPuller(tagId), mCallback(callback) {
-        VLOG("StatsCallbackPuller created for tag %d", tagId);
+StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
+                                         const int64_t coolDownNs, int64_t timeoutNs,
+                                         const vector<int> additiveFields)
+    : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) {
+    VLOG("StatsCallbackPuller created for tag %d", tagId);
 }
 
 bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    VLOG("StatsCallbackPuller called for tag %d", mTagId)
+    VLOG("StatsCallbackPuller called for tag %d", mTagId);
     if(mCallback == nullptr) {
         ALOGW("No callback registered");
         return false;
     }
-    int64_t wallClockTimeNs = getWallClockNs();
-    int64_t elapsedTimeNs = getElapsedRealtimeNs();
-    vector<StatsLogEventWrapper> returned_value;
-    Status status = mCallback->pullData(mTagId, elapsedTimeNs, wallClockTimeNs, &returned_value);
+
+    // Shared variables needed in the result receiver.
+    shared_ptr<mutex> cv_mutex = make_shared<mutex>();
+    shared_ptr<condition_variable> cv = make_shared<condition_variable>();
+    shared_ptr<bool> pullFinish = make_shared<bool>(false);
+    shared_ptr<bool> pullSuccess = make_shared<bool>(false);
+    shared_ptr<vector<shared_ptr<LogEvent>>> sharedData =
+            make_shared<vector<shared_ptr<LogEvent>>>();
+
+    shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>(
+            [cv_mutex, cv, pullFinish, pullSuccess, sharedData](
+                    int32_t atomTag, bool success, const vector<StatsEventParcel>& output) {
+                // This is the result of the pull, executing in a statsd binder thread.
+                // The pull could have taken a long time, and we should only modify
+                // data (the output param) if the pointer is in scope and the pull did not time out.
+                {
+                    lock_guard<mutex> lk(*cv_mutex);
+                    for (const StatsEventParcel& parcel: output) {
+                        shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
+                        bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
+                                                        parcel.buffer.size());
+                        if (valid) {
+                            sharedData->push_back(event);
+                        } else {
+                            StatsdStats::getInstance().noteAtomError(event->GetTagId(),
+                                                                     /*pull=*/true);
+                        }
+                    }
+                    *pullSuccess = success;
+                    *pullFinish = true;
+                }
+                cv->notify_one();
+            });
+
+    // Initiate the pull. This is a oneway call to a different process, except
+    // in unit tests. In process calls are not oneway.
+    Status status = mCallback->onPullAtom(mTagId, resultReceiver);
     if (!status.isOk()) {
-        ALOGW("StatsCallbackPuller::pull failed for %d", mTagId);
+        StatsdStats::getInstance().notePullBinderCallFailed(mTagId);
         return false;
     }
-    data->clear();
-    for (const StatsLogEventWrapper& it: returned_value) {
-        LogEvent::createLogEvents(it, *data);
+
+    {
+        unique_lock<mutex> unique_lk(*cv_mutex);
+        // Wait until the pull finishes, or until the pull timeout.
+        cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs),
+                     [pullFinish] { return *pullFinish; });
+        if (!*pullFinish) {
+            // Note: The parent stats puller will also note that there was a timeout and that the
+            // cache should be cleared. Once we migrate all pullers to this callback, we could
+            // consolidate the logic.
+            return true;
+        } else {
+            // Only copy the data if we did not timeout and the pull was successful.
+            if (*pullSuccess) {
+                *data = std::move(*sharedData);
+            }
+            VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId);
+            return *pullSuccess;
+        }
     }
-    VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId);
-    return true;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.h b/cmds/statsd/src/external/StatsCallbackPuller.h
index 03e78be..e82e8bb 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.h
+++ b/cmds/statsd/src/external/StatsCallbackPuller.h
@@ -16,21 +16,29 @@
 
 #pragma once
 
-#include <android/os/IStatsPullerCallback.h>
-
+#include <aidl/android/os/IPullAtomCallback.h>
 #include "StatsPuller.h"
 
+using aidl::android::os::IPullAtomCallback;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 class StatsCallbackPuller : public StatsPuller {
 public:
-    explicit StatsCallbackPuller(int tagId, const sp<IStatsPullerCallback>& callback);
+    explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
+                                 const int64_t coolDownNs, const int64_t timeoutNs,
+                                 const std::vector<int> additiveFields);
 
 private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
-    const sp<IStatsPullerCallback> mCallback;
+    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
+    const shared_ptr<IPullAtomCallback> mCallback;
+
+    FRIEND_TEST(StatsCallbackPullerTest, PullFail);
+    FRIEND_TEST(StatsCallbackPullerTest, PullSuccess);
+    FRIEND_TEST(StatsCallbackPullerTest, PullTimeout);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
deleted file mode 100644
index f37d2be..0000000
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
+++ /dev/null
@@ -1,78 +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 DEBUG false
-#include "Log.h"
-
-#include <android/os/IStatsCompanionService.h>
-#include <binder/IPCThreadState.h>
-#include <private/android_filesystem_config.h>
-#include "../stats_log_util.h"
-#include "../statscompanion_util.h"
-#include "StatsCompanionServicePuller.h"
-
-using namespace android;
-using namespace android::base;
-using namespace android::binder;
-using namespace android::os;
-using std::make_shared;
-using std::shared_ptr;
-using std::vector;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
-// let StatsCompanionService handle that and send the data back.
-StatsCompanionServicePuller::StatsCompanionServicePuller(int tagId) : StatsPuller(tagId) {
-}
-
-void StatsCompanionServicePuller::SetStatsCompanionService(
-        sp<IStatsCompanionService> statsCompanionService) {
-    AutoMutex _l(mStatsCompanionServiceLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
-    mStatsCompanionService = statsCompanionService;
-}
-
-bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* data) {
-    sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
-    if (statsCompanionServiceCopy != nullptr) {
-        vector<StatsLogEventWrapper> returned_value;
-        Status status = statsCompanionServiceCopy->pullData(mTagId, &returned_value);
-        if (!status.isOk()) {
-            ALOGW("StatsCompanionServicePuller::pull failed for %d", mTagId);
-            StatsdStats::getInstance().noteStatsCompanionPullFailed(mTagId);
-            if (status.exceptionCode() == Status::Exception::EX_TRANSACTION_FAILED) {
-                StatsdStats::getInstance().noteStatsCompanionPullBinderTransactionFailed(mTagId);
-            }
-            return false;
-        }
-        data->clear();
-        for (const StatsLogEventWrapper& it : returned_value) {
-            LogEvent::createLogEvents(it, *data);
-        }
-        VLOG("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
-        return true;
-    } else {
-        ALOGW("statsCompanion not found!");
-        return false;
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.h b/cmds/statsd/src/external/StatsCompanionServicePuller.h
deleted file mode 100644
index 2e13320..0000000
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.h
+++ /dev/null
@@ -1,40 +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.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StatsCompanionServicePuller : public StatsPuller {
-public:
-    explicit StatsCompanionServicePuller(int tagId);
-
-    void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) override;
-
-private:
-    Mutex mStatsCompanionServiceLock;
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
-    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index 9552c0a..bb5d0a6 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -32,50 +32,64 @@
 sp<UidMap> StatsPuller::mUidMap = nullptr;
 void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }
 
-StatsPuller::StatsPuller(const int tagId)
-    : mTagId(tagId), mLastPullTimeNs(0) {
+StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs,
+                         const std::vector<int> additiveFields)
+    : mTagId(tagId),
+      mPullTimeoutNs(pullTimeoutNs),
+      mCoolDownNs(coolDownNs),
+      mAdditiveFields(additiveFields),
+      mLastPullTimeNs(0),
+      mLastEventTimeNs(0) {
 }
 
-bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) {
+bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data) {
     lock_guard<std::mutex> lock(mLock);
-    int64_t elapsedTimeNs = getElapsedRealtimeNs();
+    const int64_t elapsedTimeNs = getElapsedRealtimeNs();
+    const int64_t systemUptimeMillis = getSystemUptimeMillis();
     StatsdStats::getInstance().notePull(mTagId);
-    const bool shouldUseCache = elapsedTimeNs - mLastPullTimeNs <
-                                StatsPullerManager::kAllPullAtomInfo.at(mTagId).coolDownNs;
+    const bool shouldUseCache =
+            (mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs);
     if (shouldUseCache) {
         if (mHasGoodData) {
             (*data) = mCachedData;
             StatsdStats::getInstance().notePullFromCache(mTagId);
+
         }
         return mHasGoodData;
     }
-
     if (mLastPullTimeNs > 0) {
         StatsdStats::getInstance().updateMinPullIntervalSec(
                 mTagId, (elapsedTimeNs - mLastPullTimeNs) / NS_PER_SEC);
     }
     mCachedData.clear();
     mLastPullTimeNs = elapsedTimeNs;
+    mLastEventTimeNs = eventTimeNs;
     mHasGoodData = PullInternal(&mCachedData);
     if (!mHasGoodData) {
         return mHasGoodData;
     }
-    const int64_t pullDurationNs = getElapsedRealtimeNs() - elapsedTimeNs;
-    StatsdStats::getInstance().notePullTime(mTagId, pullDurationNs);
-    const bool pullTimeOut =
-            pullDurationNs > StatsPullerManager::kAllPullAtomInfo.at(mTagId).pullTimeoutNs;
+    const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs;
+    const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis;
+    StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs);
+    const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs;
     if (pullTimeOut) {
         // Something went wrong. Discard the data.
-        clearCacheLocked();
+        mCachedData.clear();
         mHasGoodData = false;
-        StatsdStats::getInstance().notePullTimeout(mTagId);
+        StatsdStats::getInstance().notePullTimeout(
+                mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs));
         ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId,
-              (long long)pullDurationNs);
+              (long long)pullElapsedDurationNs);
         return mHasGoodData;
     }
 
     if (mCachedData.size() > 0) {
-        mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId);
+        mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields);
+    }
+
+    if (mCachedData.empty()) {
+        VLOG("Data pulled is empty");
+        StatsdStats::getInstance().noteEmptyData(mTagId);
     }
 
     (*data) = mCachedData;
@@ -95,12 +109,12 @@
     int ret = mCachedData.size();
     mCachedData.clear();
     mLastPullTimeNs = 0;
+    mLastEventTimeNs = 0;
     return ret;
 }
 
 int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) {
-    if (timestampNs - mLastPullTimeNs >
-        StatsPullerManager::kAllPullAtomInfo.at(mTagId).coolDownNs) {
+    if (timestampNs - mLastPullTimeNs > mCoolDownNs) {
         return clearCache();
     } else {
         return 0;
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index c83c4f8..470d15e 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
 #include <mutex>
 #include <vector>
@@ -26,13 +26,19 @@
 #include "logd/LogEvent.h"
 #include "puller_util.h"
 
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 class StatsPuller : public virtual RefBase {
 public:
-    explicit StatsPuller(const int tagId);
+    explicit StatsPuller(const int tagId,
+                         const int64_t coolDownNs = NS_PER_SEC,
+                         const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs,
+                         const std::vector<int> additiveFields = std::vector<int>());
 
     virtual ~StatsPuller() {}
 
@@ -45,7 +51,7 @@
     //   2) pull takes longer than mPullTimeoutNs (intrinsic to puller)
     // If a metric wants to make any change to the data, like timestamps, it
     // should make a copy as this data may be shared with multiple metrics.
-    bool Pull(std::vector<std::shared_ptr<LogEvent>>* data);
+    bool Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data);
 
     // Clear cache immediately
     int ForceClearCache();
@@ -55,11 +61,18 @@
 
     static void SetUidMap(const sp<UidMap>& uidMap);
 
-    virtual void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService){};
+    virtual void SetStatsCompanionService(
+            shared_ptr<IStatsCompanionService> statsCompanionService) {};
 
 protected:
     const int mTagId;
 
+    // Max time allowed to pull this atom.
+    // We cannot reliably kill a pull thread. So we don't terminate the puller.
+    // The data is discarded if the pull takes longer than this and mHasGoodData
+    // marked as false.
+    const int64_t mPullTimeoutNs = StatsdStats::kPullMaxDelayNs;
+
 private:
     mutable std::mutex mLock;
 
@@ -68,8 +81,24 @@
 
     bool mHasGoodData = false;
 
+    // Minimum time before this puller does actual pull again.
+    // Pullers can cause significant impact to system health and battery.
+    // So that we don't pull too frequently.
+    // If a pull request comes before cooldown, a cached version from previous pull
+    // will be returned.
+    const int64_t mCoolDownNs = 1 * NS_PER_SEC;
+
+    // The field numbers of the fields that need to be summed when merging
+    // isolated uid with host uid.
+    const std::vector<int> mAdditiveFields;
+
     int64_t mLastPullTimeNs;
 
+    // All pulls happen due to an event (app upgrade, bucket boundary, condition change, etc).
+    // If multiple pulls need to be done at the same event time, we will always use the cache after
+    // the first pull.
+    int64_t mLastEventTimeNs;
+
     // Cache of data from last pull. If next request comes before cool down finishes,
     // cached data will be returned.
     // Cached data is cleared when
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 0b0e4f6..8a9ec74 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -17,26 +17,22 @@
 #define DEBUG false
 #include "Log.h"
 
-#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsPullerCallback.h>
+#include "StatsPullerManager.h"
+
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
+
 #include <algorithm>
+#include <iostream>
+
 #include "../StatsService.h"
 #include "../logd/LogEvent.h"
 #include "../stats_log_util.h"
 #include "../statscompanion_util.h"
-#include "CarStatsPuller.h"
-#include "GpuStatsPuller.h"
-#include "PowerStatsPuller.h"
-#include "ResourceHealthManagerPuller.h"
 #include "StatsCallbackPuller.h"
-#include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
-#include "SubsystemSleepStatePuller.h"
 #include "TrainInfoPuller.h"
-#include "statslog.h"
+#include "statslog_statsd.h"
 
 using std::shared_ptr;
 using std::vector;
@@ -45,254 +41,132 @@
 namespace os {
 namespace statsd {
 
+// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
+// pullAtomCallbackDied is never called.
+struct PullAtomCallbackDeathCookie {
+    PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
+                                const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
+            mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+    }
+
+    wp<StatsPullerManager> mPullerManager;
+    PullerKey mPullerKey;
+    wp<StatsPuller> mPuller;
+};
+
+void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
+    PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
+    sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
+    if (!thiz) {
+        return;
+    }
+
+    const PullerKey& pullerKey = cookie_->mPullerKey;
+    wp<StatsPuller> puller = cookie_->mPuller;
+
+    // Erase the mapping from the puller key to the puller if the mapping still exists.
+    // Note that we are removing the StatsPuller object, which internally holds the binder
+    // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
+    lock_guard<mutex> lock(thiz->mLock);
+    const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
+    if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
+                                                                         /*registered=*/false);
+        thiz->kAllPullAtomInfo.erase(pullerKey);
+    }
+    // The death recipient corresponding to this specific IPullAtomCallback can never
+    // be triggered again, so free up resources.
+    delete cookie_;
+}
+
 // Values smaller than this may require to update the alarm.
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
-std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
-        // wifi_bytes_transfer
-        {android::util::WIFI_BYTES_TRANSFER,
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
-        // wifi_bytes_transfer_by_fg_bg
-        {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
-         {.additiveFields = {3, 4, 5, 6},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
-        // mobile_bytes_transfer
-        {android::util::MOBILE_BYTES_TRANSFER,
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
-        // mobile_bytes_transfer_by_fg_bg
-        {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
-         {.additiveFields = {3, 4, 5, 6},
-          .puller =
-                  new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
-        // bluetooth_bytes_transfer
-        {android::util::BLUETOOTH_BYTES_TRANSFER,
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
-        // kernel_wakelock
-        {android::util::KERNEL_WAKELOCK,
-         {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
-        // subsystem_sleep_state
-        {android::util::SUBSYSTEM_SLEEP_STATE, {.puller = new SubsystemSleepStatePuller()}},
-        // on_device_power_measurement
-        {android::util::ON_DEVICE_POWER_MEASUREMENT, {.puller = new PowerStatsPuller()}},
-        // cpu_time_per_freq
-        {android::util::CPU_TIME_PER_FREQ,
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
-        // cpu_time_per_uid
-        {android::util::CPU_TIME_PER_UID,
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
-        // cpu_time_per_uid_freq
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {android::util::CPU_TIME_PER_UID_FREQ,
-         {.additiveFields = {4},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
-        // cpu_active_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {android::util::CPU_ACTIVE_TIME,
-         {.additiveFields = {2},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
-        // cpu_cluster_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {android::util::CPU_CLUSTER_TIME,
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
-        // wifi_activity_energy_info
-        {android::util::WIFI_ACTIVITY_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
-        // modem_activity_info
-        {android::util::MODEM_ACTIVITY_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
-        // bluetooth_activity_info
-        {android::util::BLUETOOTH_ACTIVITY_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
-        // system_elapsed_realtime
-        {android::util::SYSTEM_ELAPSED_REALTIME,
-         {.coolDownNs = NS_PER_SEC,
-          .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
-          .pullTimeoutNs = NS_PER_SEC / 2,
-         }},
-        // system_uptime
-        {android::util::SYSTEM_UPTIME,
-         {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
-        // remaining_battery_capacity
-        {android::util::REMAINING_BATTERY_CAPACITY,
-         {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
-        // full_battery_capacity
-        {android::util::FULL_BATTERY_CAPACITY,
-         {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
-        // battery_voltage
-        {android::util::BATTERY_VOLTAGE,
-         {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
-        // battery_level
-        {android::util::BATTERY_LEVEL,
-         {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
-        // battery_cycle_count
-        {android::util::BATTERY_CYCLE_COUNT,
-         {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
-        // process_memory_state
-        {android::util::PROCESS_MEMORY_STATE,
-         {.additiveFields = {4, 5, 6, 7, 8, 9},
-          .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
-        // native_process_memory_state
-        {android::util::NATIVE_PROCESS_MEMORY_STATE,
-         {.additiveFields = {3, 4, 5, 6, 8},
-          .puller = new StatsCompanionServicePuller(android::util::NATIVE_PROCESS_MEMORY_STATE)}},
-        // process_memory_high_water_mark
-        {android::util::PROCESS_MEMORY_HIGH_WATER_MARK,
-         {.additiveFields = {3},
-          .puller =
-                  new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
-        // system_ion_heap_size
-        {android::util::SYSTEM_ION_HEAP_SIZE,
-         {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
-        // process_system_ion_heap_size
-        {android::util::PROCESS_SYSTEM_ION_HEAP_SIZE,
-         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
-        // temperature
-        {android::util::TEMPERATURE,
-         {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
-        // cooling_device
-        {android::util::COOLING_DEVICE,
-         {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
-        // binder_calls
-        {android::util::BINDER_CALLS,
-         {.additiveFields = {4, 5, 6, 8, 12},
-          .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
-        // binder_calls_exceptions
-        {android::util::BINDER_CALLS_EXCEPTIONS,
-         {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
-        // looper_stats
-        {android::util::LOOPER_STATS,
-         {.additiveFields = {5, 6, 7, 8, 9},
-          .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
-        // Disk Stats
-        {android::util::DISK_STATS,
-         {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
-        // Directory usage
-        {android::util::DIRECTORY_USAGE,
-         {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
-        // Size of app's code, data, and cache
-        {android::util::APP_SIZE,
-         {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
-        // Size of specific categories of files. Eg. Music.
-        {android::util::CATEGORY_SIZE,
-         {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
-        // Number of fingerprints enrolled for each user.
-        {android::util::NUM_FINGERPRINTS_ENROLLED,
-         {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
-        // Number of faces enrolled for each user.
-        {android::util::NUM_FACES_ENROLLED,
-         {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
-        // ProcStats.
-        {android::util::PROC_STATS,
-         {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
-        // ProcStatsPkgProc.
-        {android::util::PROC_STATS_PKG_PROC,
-         {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
-        // Disk I/O stats per uid.
-        {android::util::DISK_IO,
-         {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
-          .coolDownNs = 3 * NS_PER_SEC,
-          .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
-        // PowerProfile constants for power model calculations.
-        {android::util::POWER_PROFILE,
-         {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
-        // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
-        {android::util::PROCESS_CPU_TIME,
-         {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
-          .puller = new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}},
-        {android::util::CPU_TIME_PER_THREAD_FREQ,
-         {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
-        // DeviceCalculatedPowerUse.
-        {android::util::DEVICE_CALCULATED_POWER_USE,
-         {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
-        // DeviceCalculatedPowerBlameUid.
-        {android::util::DEVICE_CALCULATED_POWER_BLAME_UID,
-         {.puller = new StatsCompanionServicePuller(
-                  android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
-        // DeviceCalculatedPowerBlameOther.
-        {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
-         {.puller = new StatsCompanionServicePuller(
-                  android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
-        // DebugElapsedClock.
-        {android::util::DEBUG_ELAPSED_CLOCK,
-         {.additiveFields = {1, 2, 3, 4},
-          .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
-        // DebugFailingElapsedClock.
-        {android::util::DEBUG_FAILING_ELAPSED_CLOCK,
-         {.additiveFields = {1, 2, 3, 4},
-          .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
-        // BuildInformation.
-        {android::util::BUILD_INFORMATION,
-         {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
-        // RoleHolder.
-        {android::util::ROLE_HOLDER,
-         {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
-        // PermissionState.
-        {android::util::DANGEROUS_PERMISSION_STATE,
-         {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
-        // TrainInfo.
-        {android::util::TRAIN_INFO, {.puller = new TrainInfoPuller()}},
-        // TimeZoneDataInfo.
-        {android::util::TIME_ZONE_DATA_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
-        // ExternalStorageInfo
-        {android::util::EXTERNAL_STORAGE_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
-        // GpuStatsGlobalInfo
-        {android::util::GPU_STATS_GLOBAL_INFO,
-         {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
-        // GpuStatsAppInfo
-        {android::util::GPU_STATS_APP_INFO,
-         {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
-        // AppsOnExternalStorageInfo
-        {android::util::APPS_ON_EXTERNAL_STORAGE_INFO,
-         {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
-        // Face Settings
-        {android::util::FACE_SETTINGS,
-         {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
-        // App ops
-        {android::util::APP_OPS,
-         {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
-        // VmsClientStats
-        {android::util::VMS_CLIENT_STATS,
-         {.additiveFields = {5, 6, 7, 8, 9, 10},
-          .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
-        // NotiifcationRemoteViews.
-        {android::util::NOTIFICATION_REMOTE_VIEWS,
-         {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
-};
-
-StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
+StatsPullerManager::StatsPullerManager()
+    : kAllPullAtomInfo({
+              // TrainInfo.
+              {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
+      }),
+      mNextPullTimeNs(NO_ALARM_UPDATE),
+      mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
 }
 
-bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
-    VLOG("Initiating pulling %d", tagId);
+bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
+                              vector<shared_ptr<LogEvent>>* data, bool useUids) {
+    std::lock_guard<std::mutex> _l(mLock);
+    return PullLocked(tagId, configKey, eventTimeNs, data, useUids);
+}
 
-    if (kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end()) {
-        bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(data);
-        VLOG("pulled %d items", (int)data->size());
-        if (!ret) {
-            StatsdStats::getInstance().notePullFailed(tagId);
+bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
+                              vector<std::shared_ptr<LogEvent>>* data, bool useUids) {
+    std::lock_guard<std::mutex> _l(mLock);
+    return PullLocked(tagId, uids, eventTimeNs, data, useUids);
+}
+
+bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey,
+                                    const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data,
+                                    bool useUids) {
+    vector<int32_t> uids;
+    if (useUids) {
+        auto uidProviderIt = mPullUidProviders.find(configKey);
+        if (uidProviderIt == mPullUidProviders.end()) {
+            ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId,
+                  configKey.ToString().c_str());
+            StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
+            return false;
         }
-        return ret;
+        sp<PullUidProvider> pullUidProvider = uidProviderIt->second.promote();
+        if (pullUidProvider == nullptr) {
+            ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId,
+                  configKey.ToString().c_str());
+            StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
+            return false;
+        }
+        uids = pullUidProvider->getPullAtomUids(tagId);
+    }
+    return PullLocked(tagId, uids, eventTimeNs, data, useUids);
+}
+
+bool StatsPullerManager::PullLocked(int tagId, const vector<int32_t>& uids,
+                                    const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data,
+                                    bool useUids) {
+    VLOG("Initiating pulling %d", tagId);
+    if (useUids) {
+        for (int32_t uid : uids) {
+            PullerKey key = {.atomTag = tagId, .uid = uid};
+            auto pullerIt = kAllPullAtomInfo.find(key);
+            if (pullerIt != kAllPullAtomInfo.end()) {
+                bool ret = pullerIt->second->Pull(eventTimeNs, data);
+                VLOG("pulled %zu items", data->size());
+                if (!ret) {
+                    StatsdStats::getInstance().notePullFailed(tagId);
+                }
+                return ret;
+            }
+        }
+        StatsdStats::getInstance().notePullerNotFound(tagId);
+        ALOGW("StatsPullerManager: Unknown tagId %d", tagId);
+        return false;  // Return early since we don't know what to pull.
     } else {
-        VLOG("Unknown tagId %d", tagId);
+        PullerKey key = {.atomTag = tagId, .uid = -1};
+        auto pullerIt = kAllPullAtomInfo.find(key);
+        if (pullerIt != kAllPullAtomInfo.end()) {
+            bool ret = pullerIt->second->Pull(eventTimeNs, data);
+            VLOG("pulled %zu items", data->size());
+            if (!ret) {
+                StatsdStats::getInstance().notePullFailed(tagId);
+            }
+            return ret;
+        }
+        ALOGW("StatsPullerManager: Unknown tagId %d", tagId);
         return false;  // Return early since we don't know what to pull.
     }
 }
 
 bool StatsPullerManager::PullerForMatcherExists(int tagId) const {
-    // Vendor pulled atoms might be registered after we parse the config.
-    return isVendorPulledAtom(tagId) || kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end();
+    // Pulled atoms might be registered after we parse the config, so just make sure the id is in
+    // an appropriate range.
+    return isVendorPulledAtom(tagId) || isPulledAtom(tagId);
 }
 
 void StatsPullerManager::updateAlarmLocked() {
@@ -301,9 +175,9 @@
         return;
     }
 
-    sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
-    if (statsCompanionServiceCopy != nullptr) {
-        statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000);
+    // TODO(b/151045771): do not hold a lock while making a binder call
+    if (mStatsCompanionService != nullptr) {
+        mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000);
     } else {
         VLOG("StatsCompanionService not available. Alarm not set.");
     }
@@ -311,22 +185,23 @@
 }
 
 void StatsPullerManager::SetStatsCompanionService(
-        sp<IStatsCompanionService> statsCompanionService) {
-    AutoMutex _l(mLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+        shared_ptr<IStatsCompanionService> statsCompanionService) {
+    std::lock_guard<std::mutex> _l(mLock);
+    shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
-        pulledAtom.second.puller->SetStatsCompanionService(statsCompanionService);
+        pulledAtom.second->SetStatsCompanionService(statsCompanionService);
     }
     if (mStatsCompanionService != nullptr) {
         updateAlarmLocked();
     }
 }
 
-void StatsPullerManager::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver,
-                                              int64_t nextPullTimeNs, int64_t intervalNs) {
-    AutoMutex _l(mLock);
-    auto& receivers = mReceivers[tagId];
+void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey,
+                                          wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
+                                          int64_t intervalNs) {
+    std::lock_guard<std::mutex> _l(mLock);
+    auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}];
     for (auto it = receivers.begin(); it != receivers.end(); it++) {
         if (it->receiver == receiver) {
             VLOG("Receiver already registered of %d", (int)receivers.size());
@@ -358,13 +233,15 @@
     VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size());
 }
 
-void StatsPullerManager::UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver) {
-    AutoMutex _l(mLock);
-    if (mReceivers.find(tagId) == mReceivers.end()) {
+void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey,
+                                            wp<PullDataReceiver> receiver) {
+    std::lock_guard<std::mutex> _l(mLock);
+    auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey});
+    if (receiversIt == mReceivers.end()) {
         VLOG("Unknown pull code or no receivers: %d", tagId);
         return;
     }
-    auto& receivers = mReceivers.find(tagId)->second;
+    std::list<ReceiverInfo>& receivers = receiversIt->second;
     for (auto it = receivers.begin(); it != receivers.end(); it++) {
         if (receiver == it->receiver) {
             receivers.erase(it);
@@ -374,16 +251,30 @@
     }
 }
 
+void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey,
+                                                 wp<PullUidProvider> provider) {
+    std::lock_guard<std::mutex> _l(mLock);
+    mPullUidProviders[configKey] = provider;
+}
+
+void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
+                                                   wp<PullUidProvider> provider) {
+    std::lock_guard<std::mutex> _l(mLock);
+    const auto& it = mPullUidProviders.find(configKey);
+    if (it != mPullUidProviders.end() && it->second == provider) {
+        mPullUidProviders.erase(it);
+    }
+}
+
 void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
-    AutoMutex _l(mLock);
+    std::lock_guard<std::mutex> _l(mLock);
     int64_t wallClockNs = getWallClockNs();
 
     int64_t minNextPullTimeNs = NO_ALARM_UPDATE;
 
-    vector<pair<int, vector<ReceiverInfo*>>> needToPull =
-            vector<pair<int, vector<ReceiverInfo*>>>();
+    vector<pair<const ReceiverKey*, vector<ReceiverInfo*>>> needToPull;
     for (auto& pair : mReceivers) {
-        vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
+        vector<ReceiverInfo*> receivers;
         if (pair.second.size() != 0) {
             for (ReceiverInfo& receiverInfo : pair.second) {
                 if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
@@ -395,18 +286,15 @@
                 }
             }
             if (receivers.size() > 0) {
-                needToPull.push_back(make_pair(pair.first, receivers));
+                needToPull.push_back(make_pair(&pair.first, receivers));
             }
         }
     }
-
     for (const auto& pullInfo : needToPull) {
         vector<shared_ptr<LogEvent>> data;
-        bool pullSuccess = Pull(pullInfo.first, &data);
-        if (pullSuccess) {
-            StatsdStats::getInstance().notePullDelay(
-                    pullInfo.first, getElapsedRealtimeNs() - elapsedTimeNs);
-        } else {
+        bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey,
+                                      elapsedTimeNs, &data);
+        if (!pullSuccess) {
             VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs);
         }
 
@@ -448,7 +336,7 @@
 int StatsPullerManager::ForceClearPullerCache() {
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
-        totalCleared += pulledAtom.second.puller->ForceClearCache();
+        totalCleared += pulledAtom.second->ForceClearCache();
     }
     return totalCleared;
 }
@@ -456,32 +344,45 @@
 int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) {
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
-        totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampNs);
+        totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs);
     }
     return totalCleared;
 }
 
-void StatsPullerManager::RegisterPullerCallback(int32_t atomTag,
-        const sp<IStatsPullerCallback>& callback) {
-    AutoMutex _l(mLock);
-    // Platform pullers cannot be changed.
-    if (!isVendorPulledAtom(atomTag)) {
-        VLOG("RegisterPullerCallback: atom tag %d is not vendor pulled", atomTag);
+void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
+                                                  const int64_t coolDownNs, const int64_t timeoutNs,
+                                                  const vector<int32_t>& additiveFields,
+                                                  const shared_ptr<IPullAtomCallback>& callback,
+                                                  bool useUid) {
+    std::lock_guard<std::mutex> _l(mLock);
+    VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
+
+    if (callback == nullptr) {
+        ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag);
         return;
     }
-    VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
+
     StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
-    kAllPullAtomInfo[atomTag] = {.puller = new StatsCallbackPuller(atomTag, callback)};
+    int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
+    int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
+
+    sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
+                                                             actualTimeoutNs, additiveFields);
+    PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
+    AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
+                         new PullAtomCallbackDeathCookie(this, key, puller));
+    kAllPullAtomInfo[key] = puller;
 }
 
-void StatsPullerManager::UnregisterPullerCallback(int32_t atomTag) {
-    AutoMutex _l(mLock);
-    // Platform pullers cannot be changed.
-    if (!isVendorPulledAtom(atomTag)) {
-        return;
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
+                                                    bool useUids) {
+    std::lock_guard<std::mutex> _l(mLock);
+    PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
+    if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
+                                                                         /*registered=*/false);
+        kAllPullAtomInfo.erase(key);
     }
-    StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
-    kAllPullAtomInfo.erase(atomTag);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 6791f66..194a0f5 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -16,40 +16,48 @@
 
 #pragma once
 
-#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsPullerCallback.h>
-#include <binder/IServiceManager.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <utils/RefBase.h>
-#include <utils/threads.h>
+
 #include <list>
 #include <vector>
+
 #include "PullDataReceiver.h"
+#include "PullUidProvider.h"
 #include "StatsPuller.h"
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
+#include "packages/UidMap.h"
+
+using aidl::android::os::IPullAtomCallback;
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-typedef struct {
-    // The field numbers of the fields that need to be summed when merging
-    // isolated uid with host uid.
-    std::vector<int> additiveFields;
-    // Minimum time before this puller does actual pull again.
-    // Pullers can cause significant impact to system health and battery.
-    // So that we don't pull too frequently.
-    // If a pull request comes before cooldown, a cached version from previous pull
-    // will be returned.
-    int64_t coolDownNs = 1 * NS_PER_SEC;
-    // The actual puller
-    sp<StatsPuller> puller;
-    // Max time allowed to pull this atom.
-    // We cannot reliably kill a pull thread. So we don't terminate the puller.
-    // The data is discarded if the pull takes longer than this and mHasGoodData
-    // marked as false.
-    int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs;
-} PullAtomInfo;
+typedef struct PullerKey {
+    // The uid of the process that registers this puller.
+    const int uid = -1;
+    // The atom that this puller is for.
+    const int atomTag;
+
+    bool operator<(const PullerKey& that) const {
+        if (uid < that.uid) {
+            return true;
+        }
+        if (uid > that.uid) {
+            return false;
+        }
+        return atomTag < that.atomTag;
+    };
+
+    bool operator==(const PullerKey& that) const {
+        return uid == that.uid && atomTag == that.atomTag;
+    };
+} PullerKey;
 
 class StatsPullerManager : public virtual RefBase {
 public:
@@ -58,13 +66,24 @@
     virtual ~StatsPullerManager() {
     }
 
+
     // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs
     // and then every intervalNs thereafter.
-    virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
+    virtual void RegisterReceiver(int tagId, const ConfigKey& configKey,
+                                  wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
                                   int64_t intervalNs);
 
     // Stop listening on a tagId.
-    virtual void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver);
+    virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey,
+                                    wp<PullDataReceiver> receiver);
+
+    // Registers a pull uid provider for the config key. When pulling atoms, it will be used to
+    // determine which uids to pull from.
+    virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider);
+
+    // Unregister a pull uid provider.
+    virtual void UnregisterPullUidProvider(const ConfigKey& configKey,
+                                           wp<PullUidProvider> provider);
 
     // Verify if we know how to pull for this matcher
     bool PullerForMatcherExists(int tagId) const;
@@ -78,9 +97,16 @@
     // Returns false when
     //   1) the pull fails
     //   2) pull takes longer than mPullTimeoutNs (intrinsic to puller)
+    //   3) Either a PullUidProvider was not registered for the config, or the there was no puller
+    //      registered for any of the uids for this atom.
     // If the metric wants to make any change to the data, like timestamps, they
     // should make a copy as this data may be shared with multiple metrics.
-    virtual bool Pull(int tagId, vector<std::shared_ptr<LogEvent>>* data);
+    virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
+                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
+
+    // Same as above, but directly specify the allowed uids to pull from.
+    virtual bool Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
+                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
 
     // Clear pull data cache immediately.
     int ForceClearPullerCache();
@@ -88,17 +114,31 @@
     // Clear pull data cache if it is beyond respective cool down time.
     int ClearPullerCacheIfNecessary(int64_t timestampNs);
 
-    void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+    void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
 
-    void RegisterPullerCallback(int32_t atomTag,
-                                const sp<IStatsPullerCallback>& callback);
+    void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
+                                  const int64_t timeoutNs, const vector<int32_t>& additiveFields,
+                                  const shared_ptr<IPullAtomCallback>& callback,
+                                  bool useUid = true);
 
-    void UnregisterPullerCallback(int32_t atomTag);
+    void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true);
 
-    static std::map<int, PullAtomInfo> kAllPullAtomInfo;
+    std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
 
 private:
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+    const static int64_t kMinCoolDownNs = NS_PER_SEC;
+    const static int64_t kMaxTimeoutNs = 10 * NS_PER_SEC;
+    shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+    // A struct containing an atom id and a Config Key
+    typedef struct ReceiverKey {
+        const int atomTag;
+        const ConfigKey configKey;
+
+        inline bool operator<(const ReceiverKey& that) const {
+            return atomTag == that.atomTag ? configKey < that.configKey : atomTag < that.atomTag;
+        }
+    } ReceiverKey;
 
     typedef struct {
         int64_t nextPullTimeNs;
@@ -106,16 +146,34 @@
         wp<PullDataReceiver> receiver;
     } ReceiverInfo;
 
-    // mapping from simple matcher tagId to receivers
-    std::map<int, std::list<ReceiverInfo>> mReceivers;
+    // mapping from Receiver Key to receivers
+    std::map<ReceiverKey, std::list<ReceiverInfo>> mReceivers;
+
+    // mapping from Config Key to the PullUidProvider for that config
+    std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
+
+    bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
+                    vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
+
+    bool PullLocked(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
+                    vector<std::shared_ptr<LogEvent>>* data, bool useUids);
 
     // locks for data receiver and StatsCompanionService changes
-    Mutex mLock;
+    std::mutex mLock;
 
     void updateAlarmLocked();
 
     int64_t mNextPullTimeNs;
 
+    // Death recipient that is triggered when the process holding the IPullAtomCallback has died.
+    ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a pull atom callback dies.
+     * The cookie is a pointer to a PullAtomCallbackDeathCookie.
+     */
+    static void pullAtomCallbackDied(void* cookie);
+
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
@@ -123,6 +181,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
+
+    FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
deleted file mode 100644
index f6a4aea..0000000
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ /dev/null
@@ -1,378 +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 DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/power/1.0/IPower.h>
-#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/stats/1.0/IPowerStats.h>
-
-#include <fcntl.h>
-#include <hardware/power.h>
-#include <hardware_legacy/power.h>
-#include <inttypes.h>
-#include <semaphore.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include "external/SubsystemSleepStatePuller.h"
-#include "external/StatsPuller.h"
-
-#include "SubsystemSleepStatePuller.h"
-#include "logd/LogEvent.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-using android::hardware::hidl_vec;
-using android::hardware::power::V1_0::IPower;
-using android::hardware::power::V1_0::PowerStatePlatformSleepState;
-using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_1::PowerStateSubsystem;
-using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
-using android::hardware::power::stats::V1_0::PowerEntityInfo;
-using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
-using android::hardware::power::stats::V1_0::PowerEntityStateSpace;
-
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-static std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {};
-
-static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
-static sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr;
-static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
-
-static std::unordered_map<uint32_t, std::string> gEntityNames = {};
-static std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {};
-
-static std::mutex gPowerHalMutex;
-
-// The caller must be holding gPowerHalMutex.
-static void deinitPowerStatsLocked() {
-    gPowerHalV1_0 = nullptr;
-    gPowerHalV1_1 = nullptr;
-    gPowerStatsHalV1_0 = nullptr;
-}
-
-struct SubsystemSleepStatePullerDeathRecipient : virtual public hardware::hidl_death_recipient {
-    virtual void serviceDied(uint64_t cookie,
-            const wp<android::hidl::base::V1_0::IBase>& who) override {
-
-        // The HAL just died. Reset all handles to HAL services.
-        std::lock_guard<std::mutex> lock(gPowerHalMutex);
-        deinitPowerStatsLocked();
-    }
-};
-
-static sp<SubsystemSleepStatePullerDeathRecipient> gDeathRecipient =
-        new SubsystemSleepStatePullerDeathRecipient();
-
-SubsystemSleepStatePuller::SubsystemSleepStatePuller() :
-    StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) {
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool checkResultLocked(const Return<void> &ret, const char* function) {
-    if (!ret.isOk()) {
-        ALOGE("%s failed: requested HAL service not available. Description: %s",
-            function, ret.description().c_str());
-        if (ret.isDeadObject()) {
-            deinitPowerStatsLocked();
-        }
-        return false;
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-// gPowerStatsHalV1_0 must not be null
-static bool initializePowerStats() {
-    using android::hardware::power::stats::V1_0::Status;
-
-    // Clear out previous content if we are re-initializing
-    gEntityNames.clear();
-    gStateNames.clear();
-
-    Return<void> ret;
-    ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting power entity info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId to power entity name
-        for (auto info : infos) {
-            gEntityNames.emplace(info.powerEntityId, info.powerEntityName);
-        }
-    });
-    if (!checkResultLocked(ret, __func__)) {
-        return false;
-    }
-
-    ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting state info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
-        for (auto stateSpace : stateSpaces) {
-            std::unordered_map<uint32_t, std::string> stateNames = {};
-            for (auto state : stateSpace.states) {
-                stateNames.emplace(state.powerEntityStateId,
-                    state.powerEntityStateName);
-            }
-            gStateNames.emplace(stateSpace.powerEntityId, stateNames);
-        }
-    });
-    if (!checkResultLocked(ret, __func__)) {
-        return false;
-    }
-
-    return (!gEntityNames.empty()) && (!gStateNames.empty());
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getPowerStatsHalLocked() {
-    if(gPowerStatsHalV1_0 == nullptr) {
-        gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
-        if (gPowerStatsHalV1_0 == nullptr) {
-            ALOGE("Unable to get power.stats HAL service.");
-            return false;
-        }
-
-        // Link death recipient to power.stats service handle
-        hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
-        if (!linked.isOk()) {
-            ALOGE("Transaction error in linking to power.stats HAL death: %s",
-                    linked.description().c_str());
-            deinitPowerStatsLocked();
-            return false;
-        } else if (!linked) {
-            ALOGW("Unable to link to power.stats HAL death notifications");
-            // We should still continue even though linking failed
-        }
-        return initializePowerStats();
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) {
-    using android::hardware::power::stats::V1_0::Status;
-
-    if(!getPowerStatsHalLocked()) {
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
-    // Get power entity state residency data
-    bool success = false;
-    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
-        [&data, &success, wallClockTimestampNs, elapsedTimestampNs]
-        (auto results, auto status) {
-        if (status == Status::NOT_SUPPORTED) {
-            ALOGW("getPowerEntityStateResidencyData is not supported");
-            success = false;
-            return;
-        }
-
-        for(auto result : results) {
-            for(auto stateResidency : result.stateResidencyData) {
-                auto statePtr = make_shared<LogEvent>(
-                        android::util::SUBSYSTEM_SLEEP_STATE,
-                        wallClockTimestampNs, elapsedTimestampNs);
-                statePtr->write(gEntityNames.at(result.powerEntityId));
-                statePtr->write(gStateNames.at(result.powerEntityId)
-                    .at(stateResidency.powerEntityStateId));
-                statePtr->write(stateResidency.totalStateEntryCount);
-                statePtr->write(stateResidency.totalTimeInStateMs);
-                statePtr->init();
-                data->emplace_back(statePtr);
-            }
-        }
-        success = true;
-    });
-    // Intentionally not returning early here.
-    // bool success determines if this succeeded or not.
-    checkResultLocked(ret, __func__);
-
-    return success;
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getPowerHalLocked() {
-    if(gPowerHalV1_0 == nullptr) {
-        gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
-        if(gPowerHalV1_0 == nullptr) {
-            ALOGE("Unable to get power HAL service.");
-            return false;
-        }
-        gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-
-        // Link death recipient to power service handle
-        hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0);
-        if (!linked.isOk()) {
-            ALOGE("Transaction error in linking to power HAL death: %s",
-                    linked.description().c_str());
-            gPowerHalV1_0 = nullptr;
-            return false;
-        } else if (!linked) {
-            ALOGW("Unable to link to power. death notifications");
-            // We should still continue even though linking failed
-        }
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) {
-    using android::hardware::power::V1_0::Status;
-
-    if(!getPowerHalLocked()) {
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-        Return<void> ret;
-        ret = gPowerHalV1_0->getPlatformLowPowerStats(
-                [&data, wallClockTimestampNs, elapsedTimestampNs]
-                    (hidl_vec<PowerStatePlatformSleepState> states, Status status) {
-                    if (status != Status::SUCCESS) return;
-
-                    for (size_t i = 0; i < states.size(); i++) {
-                        const PowerStatePlatformSleepState& state = states[i];
-
-                        auto statePtr = make_shared<LogEvent>(
-                            android::util::SUBSYSTEM_SLEEP_STATE,
-                            wallClockTimestampNs, elapsedTimestampNs);
-                        statePtr->write(state.name);
-                        statePtr->write("");
-                        statePtr->write(state.totalTransitions);
-                        statePtr->write(state.residencyInMsecSinceBoot);
-                        statePtr->init();
-                        data->push_back(statePtr);
-                        VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
-                             (long long)state.residencyInMsecSinceBoot,
-                             (long long)state.totalTransitions,
-                             state.supportedOnlyInSuspend ? 1 : 0);
-                        for (const auto& voter : state.voters) {
-                            auto voterPtr = make_shared<LogEvent>(
-                                android::util::SUBSYSTEM_SLEEP_STATE,
-                                wallClockTimestampNs, elapsedTimestampNs);
-                            voterPtr->write(state.name);
-                            voterPtr->write(voter.name);
-                            voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
-                            voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
-                            voterPtr->init();
-                            data->push_back(voterPtr);
-                            VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
-                                 voter.name.c_str(),
-                                 (long long)voter.totalTimeInMsecVotedForSinceBoot,
-                                 (long long)voter.totalNumberOfTimesVotedSinceBoot);
-                        }
-                    }
-                });
-        if (!checkResultLocked(ret, __func__)) {
-            return false;
-        }
-
-        // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
-        sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 =
-                android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-        if (gPowerHal_1_1 != nullptr) {
-            ret = gPowerHal_1_1->getSubsystemLowPowerStats(
-            [&data, wallClockTimestampNs, elapsedTimestampNs]
-            (hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-                if (status != Status::SUCCESS) return;
-
-                if (subsystems.size() > 0) {
-                    for (size_t i = 0; i < subsystems.size(); i++) {
-                        const PowerStateSubsystem& subsystem = subsystems[i];
-                        for (size_t j = 0; j < subsystem.states.size(); j++) {
-                            const PowerStateSubsystemSleepState& state =
-                                    subsystem.states[j];
-                            auto subsystemStatePtr = make_shared<LogEvent>(
-                                android::util::SUBSYSTEM_SLEEP_STATE,
-                                wallClockTimestampNs, elapsedTimestampNs);
-                            subsystemStatePtr->write(subsystem.name);
-                            subsystemStatePtr->write(state.name);
-                            subsystemStatePtr->write(state.totalTransitions);
-                            subsystemStatePtr->write(state.residencyInMsecSinceBoot);
-                            subsystemStatePtr->init();
-                            data->push_back(subsystemStatePtr);
-                            VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
-                                 subsystem.name.c_str(), state.name.c_str(),
-                                 (long long)state.residencyInMsecSinceBoot,
-                                 (long long)state.totalTransitions,
-                                 (long long)state.lastEntryTimestampMs);
-                        }
-                    }
-                }
-            });
-        }
-        return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() {
-    std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {};
-
-    // First see if power.stats HAL is available. Fall back to power HAL if
-    // power.stats HAL is unavailable.
-    if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
-        ALOGI("Using power.stats HAL");
-        ret = getIPowerStatsDataLocked;
-    } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) {
-        ALOGI("Using power HAL");
-        ret = getIPowerDataLocked;
-    }
-
-    return ret;
-}
-
-bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    std::lock_guard<std::mutex> lock(gPowerHalMutex);
-
-    if(!gPuller) {
-        gPuller = getPullerLocked();
-    }
-
-    if(gPuller) {
-        return gPuller(data);
-    }
-
-    ALOGE("Unable to load Power Hal or power.stats HAL");
-    return false;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.h b/cmds/statsd/src/external/SubsystemSleepStatePuller.h
deleted file mode 100644
index 87f5f02..0000000
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.h
+++ /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.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads hal for sleep states
- */
-class SubsystemSleepStatePuller : public StatsPuller {
-public:
-    SubsystemSleepStatePuller();
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp
index 9d09242..3837f4a 100644
--- a/cmds/statsd/src/external/TrainInfoPuller.cpp
+++ b/cmds/statsd/src/external/TrainInfoPuller.cpp
@@ -22,7 +22,7 @@
 #include "TrainInfoPuller.h"
 #include "logd/LogEvent.h"
 #include "stats_log_util.h"
-#include "statslog.h"
+#include "statslog_statsd.h"
 #include "storage/StorageManager.h"
 
 using std::make_shared;
@@ -33,18 +33,20 @@
 namespace statsd {
 
 TrainInfoPuller::TrainInfoPuller() :
-    StatsPuller(android::util::TRAIN_INFO) {
+    StatsPuller(util::TRAIN_INFO) {
 }
 
 bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    InstallTrainInfo trainInfo;
-    bool ret = StorageManager::readTrainInfo(trainInfo);
-    if (!ret) {
-        ALOGW("Failed to read train info.");
-        return false;
+    vector<InstallTrainInfo> trainInfoList =
+        StorageManager::readAllTrainInfo();
+    if (trainInfoList.empty()) {
+        ALOGW("Train info was empty.");
+        return true;
     }
-    auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo);
-    data->push_back(event);
+    for (InstallTrainInfo& trainInfo : trainInfoList) {
+        auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo);
+        data->push_back(event);
+    }
     return true;
 }
 
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index ccfd666..aa99d00 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -17,20 +17,13 @@
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
-#include "StatsPullerManager.h"
-#include "atoms_info.h"
 #include "puller_util.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-using std::list;
-using std::map;
-using std::set;
-using std::shared_ptr;
-using std::sort;
-using std::vector;
+using namespace std;
 
 /**
  * Process all data and merge isolated with host if necessary.
@@ -54,16 +47,14 @@
  * All atoms should be of the same tagId. All fields should be present.
  */
 void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
-                                      int tagId) {
-    if (StatsPullerManager::kAllPullAtomInfo.find(tagId) ==
-        StatsPullerManager::kAllPullAtomInfo.end()) {
-        VLOG("Unknown pull atom id %d", tagId);
-        return;
-    }
-    if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) ==
-         android::util::AtomsInfo::kAtomsWithAttributionChain.end()) &&
-        (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) ==
-         android::util::AtomsInfo::kAtomsWithUidField.end())) {
+                                      int tagId, const vector<int>& additiveFieldsVec) {
+    // Check the first LogEvent for attribution chain or a uid field as either all atoms with this
+    // tagId have them or none of them do.
+    std::pair<int, int> attrIndexRange;
+    const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange);
+    bool hasUidField = (data[0]->getUidFieldIndex() != -1);
+
+    if (!hasAttributionChain && !hasUidField) {
         VLOG("No uid or attribution chain to merge, atom %d", tagId);
         return;
     }
@@ -74,31 +65,23 @@
             ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId());
             return;
         }
-        if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) !=
-            android::util::AtomsInfo::kAtomsWithAttributionChain.end()) {
-            for (auto& value : *(event->getMutableValues())) {
-                if (value.mField.getPosAtDepth(0) > kAttributionField) {
-                    break;
-                }
-                if (isAttributionUidField(value)) {
-                    const int hostUid = uidMap->getHostUidOrSelf(value.mValue.int_value);
-                    value.mValue.setInt(hostUid);
+        if (hasAttributionChain) {
+            vector<FieldValue>* const fieldValues = event->getMutableValues();
+            for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) {
+                FieldValue& fieldValue = fieldValues->at(i);
+                if (isAttributionUidField(fieldValue)) {
+                    const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value);
+                    fieldValue.mValue.setInt(hostUid);
                 }
             }
         } else {
-            auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId);
-            if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-                int uidField = it->second;  // uidField is the field number in proto,
-                // starting from 1
-                if (uidField > 0 && (int)event->getValues().size() >= uidField &&
-                    (event->getValues())[uidField - 1].mValue.getType() == INT) {
-                    Value& value = (*event->getMutableValues())[uidField - 1].mValue;
-                    const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
-                    value.setInt(hostUid);
-                } else {
-                    ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
-                    return;
-                }
+            int uidFieldIndex = event->getUidFieldIndex();
+            if (uidFieldIndex != -1) {
+                Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+                const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
+                value.setInt(hostUid);
+            } else {
+                ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
             }
         }
     }
@@ -120,8 +103,6 @@
          });
 
     vector<shared_ptr<LogEvent>> mergedData;
-    const vector<int>& additiveFieldsVec =
-            StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.additiveFields;
     const set<int> additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end());
     bool needMerge = true;
 
diff --git a/cmds/statsd/src/external/puller_util.h b/cmds/statsd/src/external/puller_util.h
index f703e6c..afcf68c 100644
--- a/cmds/statsd/src/external/puller_util.h
+++ b/cmds/statsd/src/external/puller_util.h
@@ -26,7 +26,8 @@
 namespace statsd {
 
 void mapAndMergeIsolatedUidsToHostUid(std::vector<std::shared_ptr<LogEvent>>& data,
-                                      const sp<UidMap>& uidMap, int tagId);
+                                      const sp<UidMap>& uidMap, int tagId,
+                                      const vector<int>& additiveFieldsVec);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 3054b6d..6e89038 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -20,7 +20,7 @@
 
 #include <android/util/ProtoOutputStream.h>
 #include "../stats_log_util.h"
-#include "statslog.h"
+#include "statslog_statsd.h"
 #include "storage/StorageManager.h"
 
 namespace android {
@@ -38,6 +38,7 @@
 using std::lock_guard;
 using std::shared_ptr;
 using std::string;
+using std::to_string;
 using std::vector;
 
 const int FIELD_ID_BEGIN_TIME = 1;
@@ -54,6 +55,7 @@
 
 const int FIELD_ID_ATOM_STATS_TAG = 1;
 const int FIELD_ID_ATOM_STATS_COUNT = 2;
+const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
 
 const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
 const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -113,13 +115,13 @@
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2;
 
 const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
-        {android::util::BINDER_CALLS, {6000, 10000}},
-        {android::util::LOOPER_STATS, {1500, 2500}},
-        {android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
+        {util::BINDER_CALLS, {6000, 10000}},
+        {util::LOOPER_STATS, {1500, 2500}},
+        {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
 };
 
 StatsdStats::StatsdStats() {
-    mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1);
+    mPushedAtomStats.resize(kMaxPushedAtomId + 1);
     mStartTimeSec = getWallClockSec();
 }
 
@@ -435,9 +437,18 @@
     mPulledAtomStats[pullAtomId].dataError++;
 }
 
-void StatsdStats::notePullTimeout(int pullAtomId) {
+void StatsdStats::notePullTimeout(int pullAtomId,
+                                  int64_t pullUptimeMillis,
+                                  int64_t pullElapsedMillis) {
     lock_guard<std::mutex> lock(mLock);
-    mPulledAtomStats[pullAtomId].pullTimeout++;
+    PulledAtomStats& pulledAtomStats = mPulledAtomStats[pullAtomId];
+    pulledAtomStats.pullTimeout++;
+
+    if (pulledAtomStats.pullTimeoutMetadata.size() == kMaxTimestampCount) {
+        pulledAtomStats.pullTimeoutMetadata.pop_front();
+    }
+
+    pulledAtomStats.pullTimeoutMetadata.emplace_back(pullUptimeMillis, pullElapsedMillis);
 }
 
 void StatsdStats::notePullExceedMaxDelay(int pullAtomId) {
@@ -448,7 +459,7 @@
 void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) {
     lock_guard<std::mutex> lock(mLock);
 
-    if (atomId <= android::util::kMaxPushedAtomId) {
+    if (atomId <= kMaxPushedAtomId) {
         mPushedAtomStats[atomId]++;
     } else {
         if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms) {
@@ -471,14 +482,19 @@
     mPulledAtomStats[atomId].pullFailed++;
 }
 
-void StatsdStats::noteStatsCompanionPullFailed(int atomId) {
+void StatsdStats::notePullUidProviderNotFound(int atomId) {
     lock_guard<std::mutex> lock(mLock);
-    mPulledAtomStats[atomId].statsCompanionPullFailed++;
+    mPulledAtomStats[atomId].pullUidProviderNotFound++;
 }
 
-void StatsdStats::noteStatsCompanionPullBinderTransactionFailed(int atomId) {
+void StatsdStats::notePullerNotFound(int atomId) {
     lock_guard<std::mutex> lock(mLock);
-    mPulledAtomStats[atomId].statsCompanionPullBinderTransactionFailed++;
+    mPulledAtomStats[atomId].pullerNotFound++;
+}
+
+void StatsdStats::notePullBinderCallFailed(int atomId) {
+    lock_guard<std::mutex> lock(mLock);
+    mPulledAtomStats[atomId].binderCallFailCount++;
 }
 
 void StatsdStats::noteEmptyData(int atomId) {
@@ -549,6 +565,20 @@
             std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs);
 }
 
+void StatsdStats::noteAtomError(int atomTag, bool pull) {
+    lock_guard<std::mutex> lock(mLock);
+    if (pull) {
+        mPulledAtomStats[atomTag].atomErrorCount++;
+        return;
+    }
+
+    bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end());
+    bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize);
+    if (!full || present) {
+        mPushedAtomErrorStats[atomTag]++;
+    }
+}
+
 StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
     auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
     if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -593,6 +623,7 @@
     for (auto& pullStats : mPulledAtomStats) {
         pullStats.second.totalPull = 0;
         pullStats.second.totalPullFromCache = 0;
+        pullStats.second.minPullIntervalSec = LONG_MAX;
         pullStats.second.avgPullTimeNs = 0;
         pullStats.second.maxPullTimeNs = 0;
         pullStats.second.numPullTime = 0;
@@ -602,11 +633,18 @@
         pullStats.second.dataError = 0;
         pullStats.second.pullTimeout = 0;
         pullStats.second.pullExceedMaxDelay = 0;
+        pullStats.second.pullFailed = 0;
+        pullStats.second.pullUidProviderNotFound = 0;
+        pullStats.second.pullerNotFound = 0;
         pullStats.second.registeredCount = 0;
         pullStats.second.unregisteredCount = 0;
+        pullStats.second.atomErrorCount = 0;
+        pullStats.second.binderCallFailCount = 0;
+        pullStats.second.pullTimeoutMetadata.clear();
     }
     mAtomMetricStats.clear();
     mActivationBroadcastGuardrailStats.clear();
+    mPushedAtomErrorStats.clear();
 }
 
 string buildTimeString(int64_t timeSec) {
@@ -617,6 +655,15 @@
     return string(timeBuffer);
 }
 
+int StatsdStats::getPushedAtomErrors(int atomId) const {
+    const auto& it = mPushedAtomErrorStats.find(atomId);
+    if (it != mPushedAtomErrorStats.end()) {
+        return it->second;
+    } else {
+        return 0;
+    }
+}
+
 void StatsdStats::dumpStats(int out) const {
     lock_guard<std::mutex> lock(mLock);
     time_t t = mStartTimeSec;
@@ -721,11 +768,13 @@
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
         if (mPushedAtomStats[i] > 0) {
-            dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+            dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i],
+                    getPushedAtomErrors((int)i));
         }
     }
     for (const auto& pair : mNonPlatformPushedAtomStats) {
-        dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second);
+        dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second,
+                getPushedAtomErrors(pair.first));
     }
 
     dprintf(out, "********Pulled Atom stats***********\n");
@@ -736,14 +785,32 @@
                 "  (average pull time nanos)%lld, (max pull time nanos)%lld, (average pull delay "
                 "nanos)%lld, "
                 "  (max pull delay nanos)%lld, (data error)%ld\n"
-                "  (pull timeout)%ld, (pull exceed max delay)%ld\n"
-                "  (registered count) %ld, (unregistered count) %ld\n",
+                "  (pull timeout)%ld, (pull exceed max delay)%ld"
+                "  (no uid provider count)%ld, (no puller found count)%ld\n"
+                "  (registered count) %ld, (unregistered count) %ld"
+                "  (atom error count) %d\n",
                 (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache,
                 (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec,
                 (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs,
                 (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs,
                 pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay,
-                pair.second.registeredCount, pair.second.unregisteredCount);
+                pair.second.pullUidProviderNotFound, pair.second.pullerNotFound,
+                pair.second.registeredCount, pair.second.unregisteredCount,
+                pair.second.atomErrorCount);
+        if (pair.second.pullTimeoutMetadata.size() > 0) {
+            string uptimeMillis = "(pull timeout system uptime millis) ";
+            string pullTimeoutMillis = "(pull timeout elapsed time millis) ";
+            for (const auto& stats : pair.second.pullTimeoutMetadata) {
+                uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");;
+                pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(",");
+            }
+            uptimeMillis.pop_back();
+            uptimeMillis.push_back('\n');
+            pullTimeoutMillis.pop_back();
+            pullTimeoutMillis.push_back('\n');
+            dprintf(out, "%s", uptimeMillis.c_str());
+            dprintf(out, "%s", pullTimeoutMillis.c_str());
+        }
     }
 
     if (mAnomalyAlarmRegisteredStats > 0) {
@@ -919,6 +986,10 @@
                     proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+            int errors = getPushedAtomErrors(i);
+            if (errors > 0) {
+                proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+            }
             proto.end(token);
         }
     }
@@ -928,6 +999,10 @@
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+        int errors = getPushedAtomErrors(pair.first);
+        if (errors > 0) {
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+        }
         proto.end(token);
     }
 
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 564b9ee..0050484 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,6 @@
 #pragma once
 
 #include "config/ConfigKey.h"
-#include "atoms_info.h"
 
 #include <gtest/gtest_prod.h>
 #include <log/log_time.h>
@@ -102,7 +101,7 @@
     // Per atom dimension key size limit
     static const std::map<int, std::pair<size_t, size_t>> kAtomDimensionKeySizeLimitMap;
 
-    const static int kMaxConfigCountPerUid = 10;
+    const static int kMaxConfigCountPerUid = 20;
     const static int kMaxAlertCountPerConfig = 100;
     const static int kMaxConditionCountPerConfig = 300;
     const static int kMaxMetricCountPerConfig = 1000;
@@ -119,6 +118,8 @@
 
     const static int kMaxLogSourceCount = 50;
 
+    const static int kMaxPullAtomPackages = 100;
+
     // Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd
     // drops the metrics data in memory.
     static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024;
@@ -159,13 +160,20 @@
     static const long kPullerCacheClearIntervalSec = 1;
 
     // Max time to do a pull.
-    static const int64_t kPullMaxDelayNs = 10 * NS_PER_SEC;
+    static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC;
 
     // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
     static const int kMaxNonPlatformPushedAtoms = 100;
 
-    // Max platform atom tag number.
-    static const int32_t kMaxPlatformAtomTag = 100000;
+    // Maximum atom id value that we consider a platform pushed atom.
+    // This should be updated once highest pushed atom id in atoms.proto approaches this value.
+    static const int kMaxPushedAtomId = 500;
+
+    // Atom id that is the start of the pulled atoms.
+    static const int kPullAtomStartTag = 10000;
+
+    // Atom id that is the start of vendor atoms.
+    static const int kVendorAtomStartTag = 100000;
 
     // Vendor pulled atom start id.
     static const int32_t kVendorPulledAtomStartTag = 150000;
@@ -181,6 +189,8 @@
 
     static const int64_t kInt64Max = 0x7fffffffffffffffLL;
 
+    static const int32_t kMaxLoggedBucketDropEvents = 10;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
@@ -342,7 +352,7 @@
     /*
      * Records pull exceeds timeout for the puller.
      */
-    void notePullTimeout(int pullAtomId);
+    void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis);
 
     /*
      * Records pull exceeds max delay for a metric.
@@ -361,21 +371,30 @@
                      int32_t lastAtomTag, int32_t uid, int32_t pid);
 
     /**
-     * Records that the pull of an atom has failed
+     * Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if
+     * the pull timed out, or if the outgoing binder call failed.
+     * This count will only increment if the puller was actually invoked.
+     *
+     * It does not include a pull not occurring due to not finding the appropriate
+     * puller. These cases are covered in other counts.
      */
     void notePullFailed(int atomId);
 
     /**
-     * Records that the pull of StatsCompanionService atom has failed
+     * Records that the pull of an atom has failed due to not having a uid provider.
      */
-    void noteStatsCompanionPullFailed(int atomId);
+    void notePullUidProviderNotFound(int atomId);
 
     /**
-     * Records that the pull of a StatsCompanionService atom has failed due to a failed binder
-     * transaction. This can happen when StatsCompanionService returns too
-     * much data (the max Binder parcel size is 1MB)
+     * Records that the pull of an atom has failed due not finding a puller registered by a
+     * trusted uid.
      */
-    void noteStatsCompanionPullBinderTransactionFailed(int atomId);
+    void notePullerNotFound(int atomId);
+
+    /**
+     * Records that the pull has failed due to the outgoing binder call failing.
+     */
+    void notePullBinderCallFailed(int atomId);
 
     /**
      * A pull with no data occurred
@@ -450,6 +469,16 @@
      */
      void noteActivationBroadcastGuardrailHit(const int uid);
 
+     /**
+      * Reports that an atom is erroneous or cannot be parsed successfully by
+      * statsd. An atom tag of 0 indicates that the client did not supply the
+      * atom id within the encoding.
+      *
+      * For pushed atoms only, this call should be preceded by a call to
+      * noteAtomLogged.
+      */
+     void noteAtomError(int atomTag, bool pull=false);
+
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -469,6 +498,14 @@
      */
     void dumpStats(int outFd) const;
 
+    typedef struct PullTimeoutMetadata {
+        int64_t pullTimeoutUptimeMillis;
+        int64_t pullTimeoutElapsedMillis;
+        PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) :
+            pullTimeoutUptimeMillis(uptimeMillis),
+            pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */}
+    } PullTimeoutMetadata;
+
     typedef struct {
         long totalPull = 0;
         long totalPullFromCache = 0;
@@ -483,11 +520,14 @@
         long pullTimeout = 0;
         long pullExceedMaxDelay = 0;
         long pullFailed = 0;
-        long statsCompanionPullFailed = 0;
-        long statsCompanionPullBinderTransactionFailed = 0;
+        long pullUidProviderNotFound = 0;
+        long pullerNotFound = 0;
         long emptyData = 0;
         long registeredCount = 0;
         long unregisteredCount = 0;
+        int32_t atomErrorCount = 0;
+        long binderCallFailCount = 0;
+        std::list<PullTimeoutMetadata> pullTimeoutMetadata;
     } PulledAtomStats;
 
     typedef struct {
@@ -535,6 +575,12 @@
     // Maps PullAtomId to its stats. The size is capped by the puller atom counts.
     std::map<int, PulledAtomStats> mPulledAtomStats;
 
+    // Stores the number of times a pushed atom was logged erroneously. The
+    // corresponding counts for pulled atoms are stored in PulledAtomStats.
+    // The max size of this map is kMaxAtomErrorsStatsSize.
+    std::map<int, int> mPushedAtomErrorStats;
+    int kMaxPushedAtomErrorStatsSize = 100;
+
     // Maps metric ID to its stats. The size is capped by the number of metrics.
     std::map<int64_t, AtomMetricStats> mAtomMetricStats;
 
@@ -602,6 +648,8 @@
 
     void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats);
 
+    int getPushedAtomErrors(int atomId) const;
+
     /**
      * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
      * will live as long as `this`.
@@ -620,6 +668,9 @@
     FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
+    FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
+
+    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 1d51ab8..f56fa62 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,12 +17,14 @@
 #define DEBUG false  // STOPSHIP if true
 #include "logd/LogEvent.h"
 
-#include "stats_log_util.h"
-#include "statslog.h"
-
-#include <binder/IPCThreadState.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_ibinder.h>
 #include <private/android_filesystem_config.h>
 
+#include "annotations.h"
+#include "stats_log_util.h"
+#include "statslog_statsd.h"
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -31,160 +33,41 @@
 const int FIELD_ID_EXPERIMENT_ID = 1;
 
 using namespace android::util;
+using android::base::StringPrintf;
 using android::util::ProtoOutputStream;
 using std::string;
 using std::vector;
 
-LogEvent::LogEvent(log_msg& msg) {
-    mContext =
-            create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
-    mLogdTimestampNs = msg.entry.sec * NS_PER_SEC + msg.entry.nsec;
-    mLogUid = msg.entry.uid;
-    init(mContext);
-    if (mContext) {
-        // android_log_destroy will set mContext to NULL
-        android_log_destroy(&mContext);
-    }
-}
+// stats_event.h socket types. Keep in sync.
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
 
-LogEvent::LogEvent(const LogEvent& event) {
-    mTagId = event.mTagId;
-    mLogUid = event.mLogUid;
-    mElapsedTimestampNs = event.mElapsedTimestampNs;
-    mLogdTimestampNs = event.mLogdTimestampNs;
-    mValues = event.mValues;
-}
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
 
-LogEvent::LogEvent(const StatsLogEventWrapper& statsLogEventWrapper, int workChainIndex) {
-    mTagId = statsLogEventWrapper.getTagId();
-    mLogdTimestampNs = statsLogEventWrapper.getWallClockTimeNs();
-    mElapsedTimestampNs = statsLogEventWrapper.getElapsedRealTimeNs();
-    mLogUid = 0;
-    int workChainPosOffset = 0;
-    if (workChainIndex != -1) {
-        const WorkChain& wc = statsLogEventWrapper.getWorkChains()[workChainIndex];
-        // chains are at field 1, level 2
-        int depth = 2;
-        for (int i = 0; i < (int)wc.uids.size(); i++) {
-            int pos[] = {1, i + 1, 1};
-            mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(wc.uids[i])));
-            pos[2]++;
-            mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(wc.tags[i])));
-            mValues.back().mField.decorateLastPos(2);
-        }
-        mValues.back().mField.decorateLastPos(1);
-        workChainPosOffset = 1;
-    }
-    for (int i = 0; i < (int)statsLogEventWrapper.getElements().size(); i++) {
-        Field field(statsLogEventWrapper.getTagId(), getSimpleField(i + 1 + workChainPosOffset));
-        switch (statsLogEventWrapper.getElements()[i].type) {
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::INT:
-                mValues.push_back(
-                        FieldValue(field, Value(statsLogEventWrapper.getElements()[i].int_value)));
-                break;
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::LONG:
-                mValues.push_back(
-                        FieldValue(field, Value(statsLogEventWrapper.getElements()[i].long_value)));
-                break;
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::FLOAT:
-                mValues.push_back(FieldValue(
-                        field, Value(statsLogEventWrapper.getElements()[i].float_value)));
-                break;
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::DOUBLE:
-                mValues.push_back(FieldValue(
-                        field, Value(statsLogEventWrapper.getElements()[i].double_value)));
-                break;
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::STRING:
-                mValues.push_back(
-                        FieldValue(field, Value(statsLogEventWrapper.getElements()[i].str_value)));
-                break;
-            case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::STORAGE:
-                mValues.push_back(FieldValue(
-                        field, Value(statsLogEventWrapper.getElements()[i].storage_value)));
-                break;
-            default:
-                break;
-        }
-    }
-}
-
-void LogEvent::createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper,
-                               std::vector<std::shared_ptr<LogEvent>>& logEvents) {
-    if (statsLogEventWrapper.getWorkChains().size() == 0) {
-        logEvents.push_back(std::make_shared<LogEvent>(statsLogEventWrapper, -1));
-    } else {
-        for (size_t i = 0; i < statsLogEventWrapper.getWorkChains().size(); i++) {
-            logEvents.push_back(std::make_shared<LogEvent>(statsLogEventWrapper, i));
-        }
-    }
-}
-
-LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) {
-    mLogdTimestampNs = wallClockTimestampNs;
-    mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = tagId;
-    mLogUid = 0;
-    mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
-    if (mContext) {
-        android_log_write_int64(mContext, elapsedTimestampNs);
-        android_log_write_int32(mContext, tagId);
-    }
-}
-
-LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                   int32_t uid,
-                   const std::map<int32_t, int32_t>& int_map,
-                   const std::map<int32_t, int64_t>& long_map,
-                   const std::map<int32_t, std::string>& string_map,
-                   const std::map<int32_t, float>& float_map) {
-    mLogdTimestampNs = wallClockTimestampNs;
-    mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = android::util::KEY_VALUE_PAIRS_ATOM;
-    mLogUid = uid;
-
-    int pos[] = {1, 1, 1};
-
-    mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid)));
-    pos[0]++;
-    for (const auto&itr : int_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 2;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : long_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 3;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : string_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 4;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : float_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 5;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-        mValues.at(mValues.size() - 2).mField.decorateLastPos(1);
-    }
+LogEvent::LogEvent(int32_t uid, int32_t pid)
+    : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
 }
 
 LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -192,8 +75,9 @@
                    const std::vector<uint8_t>& experimentIds, int32_t userId) {
     mLogdTimestampNs = getWallClockNs();
     mElapsedTimestampNs = getElapsedRealtimeNs();
-    mTagId = android::util::BINARY_PUSH_STATE_CHANGED;
-    mLogUid = android::IPCThreadState::self()->getCallingUid();
+    mTagId = util::BINARY_PUSH_STATE_CHANGED;
+    mLogUid = AIBinder_getCallingUid();
+    mLogPid = AIBinder_getCallingPid();
 
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode)));
@@ -210,7 +94,7 @@
                    const InstallTrainInfo& trainInfo) {
     mLogdTimestampNs = wallClockTimestampNs;
     mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = android::util::TRAIN_INFO;
+    mTagId = util::TRAIN_INFO;
 
     mValues.push_back(
             FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode)));
@@ -221,309 +105,320 @@
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
 }
 
-LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, timestampNs) {
+void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    int32_t value = readNextValue<int32_t>();
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
-    mLogdTimestampNs = timestampNs;
-    mTagId = tagId;
-    mLogUid = uid;
-    mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
-    if (mContext) {
-        android_log_write_int64(mContext, timestampNs);
-        android_log_write_int32(mContext, tagId);
+void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    int64_t value = readNextValue<int64_t>();
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
+}
+
+void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    int32_t numBytes = readNextValue<int32_t>();
+    if ((uint32_t)numBytes > mRemainingLen) {
+        mValid = false;
+        return;
     }
+
+    string value = string((char*)mBuf, numBytes);
+    mBuf += numBytes;
+    mRemainingLen -= numBytes;
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::init() {
-    if (mContext) {
-        const char* buffer;
-        size_t len = android_log_write_list_buffer(mContext, &buffer);
-        // turns to reader mode
-        android_log_context contextForRead = create_android_log_parser(buffer, len);
-        if (contextForRead) {
-            init(contextForRead);
-            // destroy the context to save memory.
-            // android_log_destroy will set mContext to NULL
-            android_log_destroy(&contextForRead);
-        }
-        android_log_destroy(&mContext);
+void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    float value = readNextValue<float>();
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
+}
+
+void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    // cast to int32_t because FieldValue does not support bools
+    int32_t value = (int32_t)readNextValue<uint8_t>();
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
+}
+
+void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    int32_t numBytes = readNextValue<int32_t>();
+    if ((uint32_t)numBytes > mRemainingLen) {
+        mValid = false;
+        return;
     }
+
+    vector<uint8_t> value(mBuf, mBuf + numBytes);
+    mBuf += numBytes;
+    mRemainingLen -= numBytes;
+    addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-LogEvent::~LogEvent() {
-    if (mContext) {
-        // This is for the case when LogEvent is created using the test interface
-        // but init() isn't called.
-        android_log_destroy(&mContext);
-    }
-}
+void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    int32_t numPairs = readNextValue<uint8_t>();
 
-bool LogEvent::write(int32_t value) {
-    if (mContext) {
-        return android_log_write_int32(mContext, value) >= 0;
-    }
-    return false;
-}
+    for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) {
+        last[1] = (pos[1] == numPairs);
 
-bool LogEvent::write(uint32_t value) {
-    if (mContext) {
-        return android_log_write_int32(mContext, value) >= 0;
-    }
-    return false;
-}
+        // parse key
+        pos[2] = 1;
+        parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
 
-bool LogEvent::write(int64_t value) {
-    if (mContext) {
-        return android_log_write_int64(mContext, value) >= 0;
-    }
-    return false;
-}
+        // parse value
+        last[2] = true;
 
-bool LogEvent::write(uint64_t value) {
-    if (mContext) {
-        return android_log_write_int64(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(const string& value) {
-    if (mContext) {
-        return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(float value) {
-    if (mContext) {
-        return android_log_write_float32(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::writeKeyValuePairs(int32_t uid,
-                                  const std::map<int32_t, int32_t>& int_map,
-                                  const std::map<int32_t, int64_t>& long_map,
-                                  const std::map<int32_t, std::string>& string_map,
-                                  const std::map<int32_t, float>& float_map) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         write(uid);
-         for (const auto& itr : int_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : long_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : string_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second.c_str());
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : float_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
-bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         for (size_t i = 0; i < nodes.size(); ++i) {
-             if (!write(nodes[i])) {
-                return false;
-             }
-         }
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
-bool LogEvent::write(const AttributionNodeInternal& node) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         if (android_log_write_int32(mContext, node.uid()) < 0) {
-            return false;
-         }
-         if (android_log_write_string8(mContext, node.tag().c_str()) < 0) {
-            return false;
-         }
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
-/**
- * The elements of each log event are stored as a vector of android_log_list_elements.
- * The goal is to do as little preprocessing as possible, because we read a tiny fraction
- * of the elements that are written to the log.
- *
- * The idea here is to read through the log items once, we get as much information we need for
- * matching as possible. Because this log will be matched against lots of matchers.
- */
-void LogEvent::init(android_log_context context) {
-    android_log_list_element elem;
-    int i = 0;
-    int depth = -1;
-    int pos[] = {1, 1, 1};
-    bool isKeyValuePairAtom = false;
-    do {
-        elem = android_log_read_next(context);
-        switch ((int)elem.type) {
-            case EVENT_TYPE_INT:
-                // elem at [0] is EVENT_TYPE_LIST, [1] is the timestamp, [2] is tag id.
-                if (i == 2) {
-                    mTagId = elem.data.int32;
-                    isKeyValuePairAtom = (mTagId == android::util::KEY_VALUE_PAIRS_ATOM);
-                } else {
-                    if (depth < 0 || depth > 2) {
-                        return;
-                    }
-
-                    mValues.push_back(
-                            FieldValue(Field(mTagId, pos, depth), Value((int32_t)elem.data.int32)));
-
-                    pos[depth]++;
-                }
+        uint8_t typeInfo = readNextValue<uint8_t>();
+        switch (getTypeId(typeInfo)) {
+            case INT32_TYPE:
+                pos[2] = 2;  // pos[2] determined by index of type in KeyValuePair in atoms.proto
+                parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
-            case EVENT_TYPE_FLOAT: {
-                if (depth < 0 || depth > 2) {
-                    ALOGE("Depth > 2. Not supported!");
-                    return;
-                }
-
-                // Handles the oneof field in KeyValuePair atom.
-                if (isKeyValuePairAtom && depth == 2) {
-                    pos[depth] = 5;
-                }
-
-                mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(elem.data.float32)));
-
-                pos[depth]++;
-
-            } break;
-            case EVENT_TYPE_STRING: {
-                if (depth < 0 || depth > 2) {
-                    ALOGE("Depth > 2. Not supported!");
-                    return;
-                }
-
-                // Handles the oneof field in KeyValuePair atom.
-                if (isKeyValuePairAtom && depth == 2) {
-                    pos[depth] = 4;
-                }
-                mValues.push_back(FieldValue(Field(mTagId, pos, depth),
-                                             Value(string(elem.data.string, elem.len))));
-
-                pos[depth]++;
-
-            } break;
-            case EVENT_TYPE_LONG: {
-                if (i == 1) {
-                    mElapsedTimestampNs = elem.data.int64;
-                } else {
-                    if (depth < 0 || depth > 2) {
-                        ALOGE("Depth > 2. Not supported!");
-                        return;
-                    }
-                    // Handles the oneof field in KeyValuePair atom.
-                    if (isKeyValuePairAtom && depth == 2) {
-                        pos[depth] = 3;
-                    }
-                    mValues.push_back(
-                            FieldValue(Field(mTagId, pos, depth), Value((int64_t)elem.data.int64)));
-
-                    pos[depth]++;
-                }
-            } break;
-            case EVENT_TYPE_LIST:
-                depth++;
-                if (depth > 2) {
-                    ALOGE("Depth > 2. Not supported!");
-                    return;
-                }
-                pos[depth] = 1;
-
+            case INT64_TYPE:
+                pos[2] = 3;
+                parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
-            case EVENT_TYPE_LIST_STOP: {
-                int prevDepth = depth;
-                depth--;
-                if (depth >= 0 && depth < 2) {
-                    // Now go back to decorate the previous items that are last at prevDepth.
-                    // So that we can later easily match them with Position=Last matchers.
-                    pos[prevDepth]--;
-                    int path = getEncodedField(pos, prevDepth, false);
-                    for (auto it = mValues.rbegin(); it != mValues.rend(); ++it) {
-                        if (it->mField.getDepth() >= prevDepth &&
-                            it->mField.getPath(prevDepth) == path) {
-                            it->mField.decorateLastPos(prevDepth);
-                        } else {
-                            // Safe to break, because the items are in DFS order.
-                            break;
-                        }
-                    }
-                    pos[depth]++;
-                }
+            case STRING_TYPE:
+                pos[2] = 4;
+                parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
-            }
-            case EVENT_TYPE_UNKNOWN:
+            case FLOAT_TYPE:
+                pos[2] = 5;
+                parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
             default:
+                mValid = false;
+        }
+    }
+
+    parseAnnotations(numAnnotations);
+
+    pos[1] = pos[2] = 1;
+    last[1] = last[2] = false;
+}
+
+void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
+                                     uint8_t numAnnotations) {
+    const unsigned int firstUidInChainIndex = mValues.size();
+    const int32_t numNodes = readNextValue<uint8_t>();
+    for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
+        last[1] = (pos[1] == numNodes);
+
+        // parse uid
+        pos[2] = 1;
+        parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
+
+        // parse tag
+        pos[2] = 2;
+        last[2] = true;
+        parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
+    }
+    // Check if at least one node was successfully parsed.
+    if (mValues.size() - 1 > firstUidInChainIndex) {
+        mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex);
+        mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1);
+    }
+
+    parseAnnotations(numAnnotations, firstUidInChainIndex);
+
+    pos[1] = pos[2] = 1;
+    last[1] = last[2] = false;
+}
+
+// Assumes that mValues is not empty
+bool LogEvent::checkPreviousValueType(Type expected) {
+    return mValues[mValues.size() - 1].mValue.getType() == expected;
+}
+
+void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    bool isUid = readNextValue<uint8_t>();
+    if (isUid) mUidFieldIndex = static_cast<int8_t>(mValues.size() - 1);
+    mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
+}
+
+void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
+    if (!mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    mTruncateTimestamp = readNextValue<uint8_t>();
+}
+
+void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    const bool primaryField = readNextValue<uint8_t>();
+    mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField);
+}
+
+void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
+                                                   int firstUidInChainIndex) {
+    if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) {
+        mValid = false;
+        return;
+    }
+
+    const bool primaryField = readNextValue<uint8_t>();
+    mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField);
+}
+
+void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    const bool exclusiveState = readNextValue<uint8_t>();
+    mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1);
+    mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState);
+}
+
+void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != INT32_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    mResetState = readNextValue<int32_t>();
+}
+
+void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    bool nested = readNextValue<uint8_t>();
+    mValues[mValues.size() - 1].mAnnotations.setNested(nested);
+}
+
+// firstUidInChainIndex is a default parameter that is only needed when parsing
+// annotations for attribution chains.
+void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
+    for (uint8_t i = 0; i < numAnnotations; i++) {
+        uint8_t annotationId = readNextValue<uint8_t>();
+        uint8_t annotationType = readNextValue<uint8_t>();
+
+        switch (annotationId) {
+            case ANNOTATION_ID_IS_UID:
+                parseIsUidAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+                parseTruncateTimestampAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_PRIMARY_FIELD:
+                parsePrimaryFieldAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
+                parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
+                break;
+            case ANNOTATION_ID_EXCLUSIVE_STATE:
+                parseExclusiveStateAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_TRIGGER_STATE_RESET:
+                parseTriggerStateResetAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_STATE_NESTED:
+                parseStateNestedAnnotation(annotationType);
+                break;
+            default:
+                mValid = false;
+                return;
+        }
+    }
+}
+
+// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
+// stats_event.c
+bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
+    mBuf = buf;
+    mRemainingLen = (uint32_t)len;
+
+    int32_t pos[] = {1, 1, 1};
+    bool last[] = {false, false, false};
+
+    // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
+    uint8_t typeInfo = readNextValue<uint8_t>();
+    if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false;
+
+    uint8_t numElements = readNextValue<uint8_t>();
+    if (numElements < 2 || numElements > 127) mValid = false;
+
+    typeInfo = readNextValue<uint8_t>();
+    if (getTypeId(typeInfo) != INT64_TYPE) mValid = false;
+    mElapsedTimestampNs = readNextValue<int64_t>();
+    numElements--;
+
+    typeInfo = readNextValue<uint8_t>();
+    if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
+    mTagId = readNextValue<int32_t>();
+    numElements--;
+    parseAnnotations(getNumAnnotations(typeInfo));  // atom-level annotations
+
+    for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
+        last[0] = (pos[0] == numElements);
+
+        typeInfo = readNextValue<uint8_t>();
+        uint8_t typeId = getTypeId(typeInfo);
+
+        switch (typeId) {
+            case BOOL_TYPE:
+                parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case INT32_TYPE:
+                parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case INT64_TYPE:
+                parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case FLOAT_TYPE:
+                parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case BYTE_ARRAY_TYPE:
+                parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case STRING_TYPE:
+                parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case KEY_VALUE_PAIRS_TYPE:
+                parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case ATTRIBUTION_CHAIN_TYPE:
+                parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
+            case ERROR_TYPE:
+                /* mErrorBitmask =*/ readNextValue<int32_t>();
+                mValid = false;
+                break;
+            default:
+                mValid = false;
                 break;
         }
-        i++;
-    } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
-    if (isKeyValuePairAtom && mValues.size() > 0) {
-        mValues[0] = FieldValue(Field(android::util::KEY_VALUE_PAIRS_ATOM, getSimpleField(1)),
-                                Value((int32_t)mLogUid));
     }
+
+    if (mRemainingLen != 0) mValid = false;
+    mBuf = nullptr;
+    return mValid;
+}
+
+uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
+    return typeInfo & 0x0F;  // type id in lower 4 bytes
+}
+
+uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
+    return (typeInfo >> 4) & 0x0F;  // num annotations in upper 4 bytes
 }
 
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
@@ -631,6 +526,26 @@
     return 0.0;
 }
 
+std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const {
+    int field = getSimpleField(key);
+    for (const auto& value : mValues) {
+        if (value.mField.getField() == field) {
+            if (value.mValue.getType() == STORAGE) {
+                return value.mValue.storage_value;
+            } else {
+                *err = BAD_TYPE;
+                return vector<uint8_t>();
+            }
+        }
+        if ((size_t)value.mField.getPosAtDepth(0) > key) {
+            break;
+        }
+    }
+
+    *err = BAD_INDEX;
+    return vector<uint8_t>();
+}
+
 string LogEvent::ToString() const {
     string result;
     result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
@@ -647,6 +562,19 @@
     writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
 }
 
+bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const {
+    if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) {
+        return false;
+    }
+
+    if (nullptr != indexRange) {
+        indexRange->first = static_cast<int>(mAttributionChainStartIndex);
+        indexRange->second = static_cast<int>(mAttributionChainEndIndex);
+    }
+
+    return true;
+}
+
 void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds,
                                std::vector<uint8_t>* protoOut) {
     ProtoOutputStream proto;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index e1b5a0b..a5f2460 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -18,9 +18,7 @@
 
 #include "FieldValue.h"
 
-#include <android/os/StatsLogEventWrapper.h>
 #include <android/util/ProtoOutputStream.h>
-#include <log/log_read.h>
 #include <private/android_logger.h>
 
 #include <string>
@@ -30,75 +28,39 @@
 namespace os {
 namespace statsd {
 
-struct AttributionNodeInternal {
-    void set_uid(int32_t id) {
-        mUid = id;
-    }
-
-    void set_tag(const std::string& value) {
-        mTag = value;
-    }
-
-    int32_t uid() const {
-        return mUid;
-    }
-
-    const std::string& tag() const {
-        return mTag;
-    }
-
-    int32_t mUid;
-    std::string mTag;
-};
-
 struct InstallTrainInfo {
     int64_t trainVersionCode;
     std::string trainName;
     int32_t status;
     std::vector<int64_t> experimentIds;
+    bool requiresStaging;
+    bool rollbackEnabled;
+    bool requiresLowLatencyMonitor;
 };
 
 /**
- * Wrapper for the log_msg structure.
+ * This class decodes the structured, serialized encoding of an atom into a
+ * vector of FieldValues.
  */
 class LogEvent {
 public:
     /**
-     * Read a LogEvent from a log_msg.
+     * \param uid user id of the logging caller
+     * \param pid process id of the logging caller
      */
-    explicit LogEvent(log_msg& msg);
+    explicit LogEvent(int32_t uid, int32_t pid);
 
     /**
-     * Creates LogEvent from StatsLogEventWrapper.
+     * Parses the atomId, timestamp, and vector of values from a buffer
+     * containing the StatsEvent/AStatsEvent encoding of an atom.
+     *
+     * \param buf a buffer that begins at the start of the serialized atom (it
+     * should not include the android_log_header_t or the StatsEventTag)
+     * \param len size of the buffer
+     *
+     * \return success of the initialization
      */
-    static void createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper,
-                                std::vector<std::shared_ptr<LogEvent>>& logEvents);
-
-    /**
-     * Construct one LogEvent from a StatsLogEventWrapper with the i-th work chain. -1 if no chain.
-     */
-    explicit LogEvent(const StatsLogEventWrapper& statsLogEventWrapper, int workChainIndex);
-
-    /**
-     * Constructs a LogEvent with synthetic data for testing. Must call init() before reading.
-     */
-    explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs);
-
-    // For testing. The timestamp is used as both elapsed real time and logd timestamp.
-    explicit LogEvent(int32_t tagId, int64_t timestampNs);
-
-    // For testing. The timestamp is used as both elapsed real time and logd timestamp.
-    explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid);
-
-    /**
-     * Constructs a KeyValuePairsAtom LogEvent from value maps.
-     */
-    explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                      int32_t uid,
-                      const std::map<int32_t, int32_t>& int_map,
-                      const std::map<int32_t, int64_t>& long_map,
-                      const std::map<int32_t, std::string>& string_map,
-                      const std::map<int32_t, float>& float_map);
+    bool parseBuffer(uint8_t* buf, size_t len);
 
     // Constructs a BinaryPushStateChanged LogEvent from API call.
     explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -108,7 +70,7 @@
     explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                       const InstallTrainInfo& installTrainInfo);
 
-    ~LogEvent();
+    ~LogEvent() {}
 
     /**
      * Get the timestamp associated with this event.
@@ -121,9 +83,17 @@
      */
     inline int GetTagId() const { return mTagId; }
 
-    inline uint32_t GetUid() const {
-        return mLogUid;
-    }
+    /**
+     * Get the uid of the logging client.
+     * Returns -1 if the uid is unknown/has not been set.
+     */
+    inline int32_t GetUid() const { return mLogUid; }
+
+    /**
+     * Get the pid of the logging client.
+     * Returns -1 if the pid is unknown/has not been set.
+     */
+    inline int32_t GetPid() const { return mLogPid; }
 
     /**
      * Get the nth value, starting at 1.
@@ -136,24 +106,7 @@
     const char* GetString(size_t key, status_t* err) const;
     bool GetBool(size_t key, status_t* err) const;
     float GetFloat(size_t key, status_t* err) const;
-
-    /**
-     * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
-     * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it.
-     */
-    bool write(uint32_t value);
-    bool write(int32_t value);
-    bool write(uint64_t value);
-    bool write(int64_t value);
-    bool write(const std::string& value);
-    bool write(float value);
-    bool write(const std::vector<AttributionNodeInternal>& nodes);
-    bool write(const AttributionNodeInternal& node);
-    bool writeKeyValuePairs(int32_t uid,
-                            const std::map<int32_t, int32_t>& int_map,
-                            const std::map<int32_t, int64_t>& long_map,
-                            const std::map<int32_t, std::string>& string_map,
-                            const std::map<int32_t, float>& float_map);
+    std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
 
     /**
      * Return a string representation of this event.
@@ -166,12 +119,6 @@
     void ToProto(android::util::ProtoOutputStream& out) const;
 
     /**
-     * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
-     * and prepares the list for reading.
-     */
-    void init();
-
-    /**
      * Set elapsed timestamp if the original timestamp is missing.
      */
     void setElapsedTimestampNs(int64_t timestampNs) {
@@ -197,39 +144,184 @@
         return &mValues;
     }
 
+    // Default value = false
+    inline bool shouldTruncateTimestamp() const {
+        return mTruncateTimestamp;
+    }
+
+    // Returns the index of the uid field within the FieldValues vector if the
+    // uid exists. If there is no uid field, returns -1.
+    //
+    // If the index within the atom definition is desired, do the following:
+    //    int vectorIndex = LogEvent.getUidFieldIndex();
+    //    if (vectorIndex != -1) {
+    //        FieldValue& v = LogEvent.getValues()[vectorIndex];
+    //        int atomIndex = v.mField.getPosAtDepth(0);
+    //    }
+    // Note that atomIndex is 1-indexed.
+    inline int getUidFieldIndex() {
+        return static_cast<int>(mUidFieldIndex);
+    }
+
+    // Returns whether this LogEvent has an AttributionChain.
+    // If it does and indexRange is not a nullptr, populate indexRange with the start and end index
+    // of the AttributionChain within mValues.
+    bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const;
+
+    // Returns the index of the exclusive state field within the FieldValues vector if
+    // an exclusive state exists. If there is no exclusive state field, returns -1.
+    //
+    // If the index within the atom definition is desired, do the following:
+    //    int vectorIndex = LogEvent.getExclusiveStateFieldIndex();
+    //    if (vectorIndex != -1) {
+    //        FieldValue& v = LogEvent.getValues()[vectorIndex];
+    //        int atomIndex = v.mField.getPosAtDepth(0);
+    //    }
+    // Note that atomIndex is 1-indexed.
+    inline int getExclusiveStateFieldIndex() const {
+        return static_cast<int>(mExclusiveStateFieldIndex);
+    }
+
+    // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+    // reset state is sent if and only if a reset should be triggered.
+    inline int getResetState() const {
+        return mResetState;
+    }
+
     inline LogEvent makeCopy() {
         return LogEvent(*this);
     }
 
+    template <class T>
+    status_t updateValue(size_t key, T& value, Type type) {
+        int field = getSimpleField(key);
+        for (auto& fieldValue : mValues) {
+            if (fieldValue.mField.getField() == field) {
+                if (fieldValue.mValue.getType() == type) {
+                    fieldValue.mValue = Value(value);
+                   return OK;
+               } else {
+                   return BAD_TYPE;
+                }
+            }
+        }
+        return BAD_INDEX;
+    }
+
+    bool isValid() const {
+        return mValid;
+    }
+
 private:
     /**
      * Only use this if copy is absolutely needed.
      */
-    LogEvent(const LogEvent&);
+    LogEvent(const LogEvent&) = default;
+
+    void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+
+    void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
+    void parseIsUidAnnotation(uint8_t annotationType);
+    void parseTruncateTimestampAnnotation(uint8_t annotationType);
+    void parsePrimaryFieldAnnotation(uint8_t annotationType);
+    void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex);
+    void parseExclusiveStateAnnotation(uint8_t annotationType);
+    void parseTriggerStateResetAnnotation(uint8_t annotationType);
+    void parseStateNestedAnnotation(uint8_t annotationType);
+    bool checkPreviousValueType(Type expected);
 
     /**
-     * Parses a log_msg into a LogEvent object.
+     * The below two variables are only valid during the execution of
+     * parseBuffer. There are no guarantees about the state of these variables
+     * before/after.
      */
-    void init(android_log_context context);
+    uint8_t* mBuf;
+    uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
+
+    bool mValid = true; // stores whether the event we received from the socket is valid
+
+    /**
+     * Side-effects:
+     *    If there is enough space in buffer to read value of type T
+     *        - move mBuf past the value that was just read
+     *        - decrement mRemainingLen by size of T
+     *    Else
+     *        - set mValid to false
+     */
+    template <class T>
+    T readNextValue() {
+        T value;
+        if (mRemainingLen < sizeof(T)) {
+            mValid = false;
+            value = 0; // all primitive types can successfully cast 0
+        } else {
+            // When alignof(T) == 1, hopefully the compiler can optimize away
+            // this conditional as always true.
+            if ((reinterpret_cast<uintptr_t>(mBuf) % alignof(T)) == 0) {
+                // We're properly aligned, and can safely make this assignment.
+                value = *((T*)mBuf);
+            } else {
+                // We need to use memcpy.  It's slower, but safe.
+                memcpy(&value, mBuf, sizeof(T));
+            }
+            mBuf += sizeof(T);
+            mRemainingLen -= sizeof(T);
+        }
+        return value;
+    }
+
+    template <class T>
+    void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) {
+        Field f = Field(mTagId, pos, depth);
+        // do not decorate last position at depth 0
+        for (int i = 1; i < depth; i++) {
+            if (last[i]) f.decorateLastPos(i);
+        }
+
+        Value v = Value(value);
+        mValues.push_back(FieldValue(f, v));
+    }
+
+    uint8_t getTypeId(uint8_t typeInfo);
+    uint8_t getNumAnnotations(uint8_t typeInfo);
 
     // The items are naturally sorted in DFS order as we read them. this allows us to do fast
     // matching.
     std::vector<FieldValue> mValues;
 
-    // This field is used when statsD wants to create log event object and write fields to it. After
-    // calling init() function, this object would be destroyed to save memory usage.
-    // When the log event is created from log msg, this field is never initiated.
-    android_log_context mContext = NULL;
-
     // The timestamp set by the logd.
     int64_t mLogdTimestampNs;
 
     // The elapsed timestamp set by statsd log writer.
     int64_t mElapsedTimestampNs;
 
-    int mTagId;
+    // The atom tag of the event (defaults to 0 if client does not
+    // appropriately set the atom id).
+    int mTagId = 0;
 
-    uint32_t mLogUid;
+    // The uid of the logging client (defaults to -1).
+    int32_t mLogUid = -1;
+
+    // The pid of the logging client (defaults to -1).
+    int32_t mLogPid = -1;
+
+    // Annotations
+    bool mTruncateTimestamp = false;
+    int mResetState = -1;
+
+    // Indexes within the FieldValue vector can be stored in 7 bits because
+    // that's the assumption enforced by the encoding used in FieldValue.
+    int8_t mUidFieldIndex = -1;
+    int8_t mAttributionChainStartIndex = -1;
+    int8_t mAttributionChainEndIndex = -1;
+    int8_t mExclusiveStateFieldIndex = -1;
 };
 
 void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 739c597..cd9c4e5 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -20,10 +20,9 @@
 #include "StatsService.h"
 #include "socket/StatsSocketListener.h"
 
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <hidl/HidlTransportSupport.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_process.h>
+#include <android/binder_manager.h>
 #include <utils/Looper.h>
 
 #include <stdio.h>
@@ -33,31 +32,35 @@
 
 using namespace android;
 using namespace android::os::statsd;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
+using std::make_shared;
 
-/**
- * Thread function data.
- */
-struct log_reader_thread_data {
-    sp<StatsService> service;
-};
+shared_ptr<StatsService> gStatsService = nullptr;
+sp<StatsSocketListener> gSocketListener = nullptr;
 
-
-sp<StatsService> gStatsService = nullptr;
-
-void sigHandler(int sig) {
-    if (gStatsService != nullptr) {
-        gStatsService->Terminate();
+void signalHandler(int sig) {
+    if (sig == SIGPIPE) {
+        // ShellSubscriber uses SIGPIPE as a signal to detect the end of the
+        // client process. Don't prematurely exit(1) here. Instead, ignore the
+        // signal and allow the write call to return EPIPE.
+        ALOGI("statsd received SIGPIPE. Ignoring signal.");
+        return;
     }
+
+    if (gSocketListener != nullptr) gSocketListener->stopListener();
+    if (gStatsService != nullptr) gStatsService->Terminate();
     ALOGW("statsd terminated on receiving signal %d.", sig);
     exit(1);
 }
 
-void registerSigHandler()
+void registerSignalHandlers()
 {
     struct sigaction sa;
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = 0;
-    sa.sa_handler = sigHandler;
+    sa.sa_handler = signalHandler;
+    sigaction(SIGPIPE, &sa, nullptr);
     sigaction(SIGHUP, &sa, nullptr);
     sigaction(SIGINT, &sa, nullptr);
     sigaction(SIGQUIT, &sa, nullptr);
@@ -69,35 +72,33 @@
     sp<Looper> looper(Looper::prepare(0 /* opts */));
 
     // Set up the binder
-    sp<ProcessState> ps(ProcessState::self());
-    ps->setThreadPoolMaxThreadCount(9);
-    ps->startThreadPool();
-    ps->giveThreadPoolName();
-    IPCThreadState::self()->disableBackgroundScheduling(true);
+    ABinderProcess_setThreadPoolMaxThreadCount(9);
+    ABinderProcess_startThreadPool();
 
     std::shared_ptr<LogEventQueue> eventQueue =
             std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
 
     // Create the service
-    gStatsService = new StatsService(looper, eventQueue);
-    if (defaultServiceManager()->addService(String16("stats"), gStatsService, false,
-                IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO)
-            != 0) {
+    gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue);
+    // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
+    // setting dumpsys priorities.
+    binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
+    if (status != STATUS_OK) {
         ALOGE("Failed to add service as AIDL service");
         return -1;
     }
 
-    registerSigHandler();
+    registerSignalHandlers();
 
     gStatsService->sayHiToStatsCompanion();
 
     gStatsService->Startup();
 
-    sp<StatsSocketListener> socketListener = new StatsSocketListener(eventQueue);
+    gSocketListener = new StatsSocketListener(eventQueue);
 
     ALOGI("Statsd starts to listen to socket.");
     // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
-    if (socketListener->startListener(600)) {
+    if (gSocketListener->startListener(600)) {
         exit(1);
     }
 
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 2cbe2e9..2b4c6a3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -81,18 +81,17 @@
     return matched;
 }
 
-bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value,
-                    const string& str_match) {
-    if (isAttributionUidField(field, value)) {
-        int uid = value.int_value;
+bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
+    if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
+        int uid = fieldValue.mValue.int_value;
         auto aidIt = UidMap::sAidToUidMapping.find(str_match);
         if (aidIt != UidMap::sAidToUidMapping.end()) {
             return ((int)aidIt->second) == uid;
         }
         std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
         return packageNames.find(str_match) != packageNames.end();
-    } else if (value.getType() == STRING) {
-        return value.str_value == str_match;
+    } else if (fieldValue.mValue.getType() == STRING) {
+        return fieldValue.mValue.str_value == str_match;
     }
     return false;
 }
@@ -228,8 +227,7 @@
         }
         case FieldValueMatcher::ValueMatcherCase::kEqString: {
             for (int i = start; i < end; i++) {
-                if (tryMatchString(uidMap, values[i].mField, values[i].mValue,
-                                   matcher.eq_string())) {
+                if (tryMatchString(uidMap, values[i], matcher.eq_string())) {
                     return true;
                 }
             }
@@ -240,7 +238,7 @@
             for (int i = start; i < end; i++) {
                 bool notEqAll = true;
                 for (const auto& str : str_list.str_value()) {
-                    if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+                    if (tryMatchString(uidMap, values[i], str)) {
                         notEqAll = false;
                         break;
                     }
@@ -255,7 +253,7 @@
             const auto& str_list = matcher.eq_any_string();
             for (int i = start; i < end; i++) {
                 for (const auto& str : str_list.str_value()) {
-                    if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+                    if (tryMatchString(uidMap, values[i], str)) {
                         return true;
                     }
                 }
@@ -357,9 +355,10 @@
 
 bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
-    if (simpleMatcher.field_value_matcher_size() <= 0) {
-        return event.GetTagId() == simpleMatcher.atom_id();
+    if (event.GetTagId() != simpleMatcher.atom_id()) {
+        return false;
     }
+
     for (const auto& matcher : simpleMatcher.field_value_matcher()) {
         if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
             return false;
diff --git a/cmds/statsd/src/metadata_util.cpp b/cmds/statsd/src/metadata_util.cpp
new file mode 100644
index 0000000..27ee59b
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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 "FieldValue.h"
+#include "metadata_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using google::protobuf::RepeatedPtrField;
+
+void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) {
+    std::string storage_value;
+    switch (value.getType()) {
+        case INT:
+            metadataFieldValue->set_value_int(value.int_value);
+            break;
+        case LONG:
+            metadataFieldValue->set_value_long(value.long_value);
+            break;
+        case FLOAT:
+            metadataFieldValue->set_value_float(value.float_value);
+            break;
+        case DOUBLE:
+            metadataFieldValue->set_value_double(value.double_value);
+            break;
+        case STRING:
+            metadataFieldValue->set_value_str(value.str_value.c_str());
+            break;
+        case STORAGE: // byte array
+            storage_value = ((char*) value.storage_value.data());
+            metadataFieldValue->set_value_storage(storage_value);
+            break;
+        default:
+            break;
+    }
+}
+
+void writeMetricDimensionKeyToMetadataDimensionKey(
+        const MetricDimensionKey& metricKey,
+        metadata::MetricDimensionKey* metadataMetricKey) {
+    for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) {
+        metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what();
+        metadata::Field* metadataField = metadataFieldValue->mutable_field();
+        metadataField->set_tag(fieldValue.mField.getTag());
+        metadataField->set_field(fieldValue.mField.getField());
+        writeValueToProto(metadataFieldValue, fieldValue.mValue);
+    }
+
+    for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) {
+        metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key();
+        metadata::Field* metadataField = metadataFieldValue->mutable_field();
+        metadataField->set_tag(fieldValue.mField.getTag());
+        metadataField->set_field(fieldValue.mField.getField());
+        writeValueToProto(metadataFieldValue, fieldValue.mValue);
+    }
+}
+
+void writeFieldValuesFromMetadata(
+        const RepeatedPtrField<metadata::FieldValue>& repeatedFieldValueList,
+        std::vector<FieldValue>* fieldValues) {
+    for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) {
+        Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field());
+        Value value;
+        switch (metadataFieldValue.value_case()) {
+            case metadata::FieldValue::ValueCase::kValueInt:
+                value = Value(metadataFieldValue.value_int());
+                break;
+            case metadata::FieldValue::ValueCase::kValueLong:
+                value = Value(metadataFieldValue.value_long());
+                break;
+            case metadata::FieldValue::ValueCase::kValueFloat:
+                value = Value(metadataFieldValue.value_float());
+                break;
+            case metadata::FieldValue::ValueCase::kValueDouble:
+                value = Value(metadataFieldValue.value_double());
+                break;
+            case metadata::FieldValue::ValueCase::kValueStr:
+                value = Value(metadataFieldValue.value_str());
+                break;
+            case metadata::FieldValue::ValueCase::kValueStorage:
+                value = Value(metadataFieldValue.value_storage());
+                break;
+            default:
+                break;
+        }
+        FieldValue fieldValue(field, value);
+        fieldValues->emplace_back(field, value);
+    }
+}
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+        const metadata::MetricDimensionKey& metricDimensionKey) {
+    std::vector<FieldValue> dimKeyInWhatFieldValues;
+    writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(),
+            &dimKeyInWhatFieldValues);
+    std::vector<FieldValue> stateValuesFieldValues;
+    writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues);
+
+    HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues);
+    HashableDimensionKey stateValues(stateValuesFieldValues);
+    MetricDimensionKey metricKey(dimKeyInWhat, stateValues);
+    return metricKey;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metadata_util.h b/cmds/statsd/src/metadata_util.h
new file mode 100644
index 0000000..84a39ff
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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 "HashableDimensionKey.h"
+
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"  // AlertMetadata
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey,
+                                                   metadata::MetricDimensionKey* metadataMetricKey);
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+        const metadata::MetricDimensionKey& metricDimensionKey);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index c023e6f..5739612 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -18,13 +18,15 @@
 #include "Log.h"
 
 #include "CountMetricProducer.h"
-#include "guardrail/StatsdStats.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
 
+#include <inttypes.h>
 #include <limits.h>
 #include <stdlib.h>
 
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
@@ -37,6 +39,7 @@
 using std::string;
 using std::unordered_map;
 using std::vector;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -48,28 +51,31 @@
 const int FIELD_ID_TIME_BASE = 9;
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
-const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
 const int FIELD_ID_IS_ACTIVE = 14;
 
 // for CountMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for CountMetricData
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
-const int FIELD_ID_DIMENSION_IN_CONDITION = 2;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
-const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5;
 // for CountBucketInfo
 const int FIELD_ID_COUNT = 3;
 const int FIELD_ID_BUCKET_NUM = 4;
 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
 
-CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
-                                         const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard,
-                                         const int64_t timeBaseNs, const int64_t startTimeNs)
-    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard) {
+CountMetricProducer::CountMetricProducer(
+        const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const int64_t timeBaseNs, const int64_t startTimeNs,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
+                     eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
     if (metric.has_bucket()) {
         mBucketSizeNs =
                 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -82,12 +88,7 @@
         mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) ||
-            HasPositionALL(metric.dimensions_in_condition());
-
-    if (metric.has_dimensions_in_condition()) {
-        translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
-    }
+    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
 
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
@@ -100,7 +101,13 @@
         mConditionSliced = true;
     }
 
-    mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
 
     flushIfNeededLocked(startTimeNs);
     // Adjust start for partial bucket
@@ -114,6 +121,14 @@
     VLOG("~CountMetricProducer() called");
 }
 
+void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                         const HashableDimensionKey& primaryKey,
+                                         const FieldValue& oldState, const FieldValue& newState) {
+    VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d",
+         (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
+         oldState.mValue.int_value, newState.mValue.int_value);
+}
+
 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
     if (mCurrentSlicedCounter == nullptr ||
         mCurrentSlicedCounter->size() == 0) {
@@ -124,10 +139,9 @@
             (unsigned long)mCurrentSlicedCounter->size());
     if (verbose) {
         for (const auto& it : *mCurrentSlicedCounter) {
-            fprintf(out, "\t(what)%s\t(condition)%s  %lld\n",
-                it.first.getDimensionKeyInWhat().toString().c_str(),
-                it.first.getDimensionKeyInCondition().toString().c_str(),
-                (unsigned long long)it.second);
+            fprintf(out, "\t(what)%s\t(state)%s  %lld\n",
+                    it.first.getDimensionKeyInWhat().toString().c_str(),
+                    it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second);
         }
     }
 }
@@ -171,13 +185,6 @@
             writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
             protoOutput->end(dimenPathToken);
         }
-        if (!mDimensionsInCondition.empty()) {
-            uint64_t dimenPathToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION);
-            writeDimensionPathToProto(mDimensionsInCondition, protoOutput);
-            protoOutput->end(dimenPathToken);
-        }
-
     }
 
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
@@ -195,22 +202,16 @@
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
             protoOutput->end(dimensionToken);
-
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                uint64_t dimensionInConditionToken = protoOutput->start(
-                        FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
-                writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(),
-                                      str_set, protoOutput);
-                protoOutput->end(dimensionInConditionToken);
-            }
         } else {
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(),
-                                               FIELD_ID_DIMENSION_LEAF_IN_CONDITION,
-                                               str_set, protoOutput);
-            }
+        }
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
         }
         // Then fill bucket_info (CountBucketInfo).
         for (const auto& bucket : counter.second) {
@@ -266,6 +267,7 @@
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("CountMetric %lld dropping data for dimension key %s",
                 (long long)mMetricId, newKey.toString().c_str());
+            StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
             return true;
         }
     }
@@ -275,12 +277,12 @@
 
 void CountMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const MetricDimensionKey& eventKey,
-        const ConditionKey& conditionKey, bool condition,
-        const LogEvent& event) {
+        const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+        const map<int, HashableDimensionKey>& statePrimaryKeys) {
     int64_t eventTimeNs = event.GetElapsedTimestampNs();
     flushIfNeededLocked(eventTimeNs);
 
-    if (condition == false) {
+    if (!condition) {
         return;
     }
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index b4a910c..f05fb06 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -17,15 +17,16 @@
 #ifndef COUNT_METRIC_PRODUCER_H
 #define COUNT_METRIC_PRODUCER_H
 
-#include <unordered_map>
-
 #include <android/util/ProtoOutputStream.h>
 #include <gtest/gtest_prod.h>
-#include "../anomaly/AnomalyTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../matchers/matcher_util.h"
+
+#include <unordered_map>
+
 #include "MetricProducer.h"
+#include "anomaly/AnomalyTracker.h"
+#include "condition/ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/matcher_util.h"
 #include "stats_util.h"
 
 namespace android {
@@ -40,17 +41,27 @@
 
 class CountMetricProducer : public MetricProducer {
 public:
-    CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int64_t timeBaseNs, const int64_t startTimeNs);
+    CountMetricProducer(
+            const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+            const int64_t timeBaseNs, const int64_t startTimeNs,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~CountMetricProducer();
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) override;
+
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKey, bool condition,
-            const LogEvent& event) override;
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
 
 private:
 
@@ -99,9 +110,11 @@
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
-    FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
-    FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
     FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
+    FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
+
+    FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket);
+    FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 96fbf7f..e9b0438 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -36,6 +36,7 @@
 using std::string;
 using std::unordered_map;
 using std::vector;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -47,30 +48,32 @@
 const int FIELD_ID_TIME_BASE = 9;
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
-const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
 const int FIELD_ID_IS_ACTIVE = 14;
 // for DurationMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for DurationMetricData
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
-const int FIELD_ID_DIMENSION_IN_CONDITION = 2;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
-const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 // for DurationBucketInfo
 const int FIELD_ID_DURATION = 3;
 const int FIELD_ID_BUCKET_NUM = 4;
 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
 
-DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const DurationMetric& metric,
-                                               const int conditionIndex, const size_t startIndex,
-                                               const size_t stopIndex, const size_t stopAllIndex,
-                                               const bool nesting,
-                                               const sp<ConditionWizard>& wizard,
-                                               const FieldMatcher& internalDimensions,
-                                               const int64_t timeBaseNs, const int64_t startTimeNs)
-    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
+DurationMetricProducer::DurationMetricProducer(
+        const ConfigKey& key, const DurationMetric& metric, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache, const size_t startIndex,
+        const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
+        const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions,
+        const int64_t timeBaseNs, const int64_t startTimeNs,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
+                     eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap),
       mAggregationType(metric.aggregation_type()),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
@@ -100,12 +103,7 @@
         ALOGE("Position ANY in dimension_in_what not supported.");
     }
 
-    if (metric.has_dimensions_in_condition()) {
-        translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
-    }
-
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) ||
-            HasPositionALL(metric.dimensions_in_condition());
+    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
 
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
@@ -115,19 +113,23 @@
             translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
             mMetric2ConditionLinks.push_back(mc);
         }
+        mConditionSliced = true;
     }
-    mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
     mUnSlicedPartCondition = ConditionState::kUnknown;
 
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
+
     mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
-    if (mWizard != nullptr && mConditionTrackerIndex >= 0) {
-        mSameConditionDimensionsInTracker =
-            mWizard->equalOutputDimensions(mConditionTrackerIndex, mDimensionsInCondition);
-        if (mMetric2ConditionLinks.size() == 1) {
-            mHasLinksToAllConditionDimensionsInTracker =
-                mWizard->equalOutputDimensions(mConditionTrackerIndex,
-                                               mMetric2ConditionLinks.begin()->conditionFields);
-        }
+    if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
+            mMetric2ConditionLinks.size() == 1) {
+        mHasLinksToAllConditionDimensionsInTracker = mWizard->equalOutputDimensions(
+                mConditionTrackerIndex, mMetric2ConditionLinks.begin()->conditionFields);
     }
     flushIfNeededLocked(startTimeNs);
     // Adjust start for partial bucket
@@ -158,33 +160,58 @@
     return anomalyTracker;
 }
 
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                            const HashableDimensionKey& primaryKey,
+                                            const FieldValue& oldState,
+                                            const FieldValue& newState) {
+    // Check if this metric has a StateMap. If so, map the new state value to
+    // the correct state group id.
+    FieldValue newStateCopy = newState;
+    mapStateValue(atomId, &newStateCopy);
+
+    flushIfNeededLocked(eventTimeNs);
+
+    // Each duration tracker is mapped to a different whatKey (a set of values from the
+    // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+    // state change event are a subset of the tracker's whatKey field values.
+    //
+    // Ex. For a duration metric dimensioned on uid and tag:
+    // DurationTracker1 whatKey = uid: 1001, tag: 1
+    // DurationTracker2 whatKey = uid: 1002, tag 1
+    //
+    // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+    // change.
+    for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+        if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+            continue;
+        }
+        whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy);
+    }
+}
+
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
         const MetricDimensionKey& eventKey) const {
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
     }
 }
 
 // SlicedConditionChange optimization case 1:
 // 1. If combination condition, logical operation is AND, only one sliced child predicate.
-// 2. No condition in dimension
-// 3. The links covers all dimension fields in the sliced child condition predicate.
+// 2. The links covers all dimension fields in the sliced child condition predicate.
 void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition,
                                                                    const int64_t eventTime) {
     if (mMetric2ConditionLinks.size() != 1 ||
-        !mHasLinksToAllConditionDimensionsInTracker ||
-        !mDimensionsInCondition.empty()) {
+        !mHasLinksToAllConditionDimensionsInTracker) {
         return;
     }
 
@@ -213,15 +240,11 @@
         mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions);
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
             HashableDimensionKey linkedConditionDimensionKey;
-            getDimensionForCondition(whatIt.first.getValues(),
-                                     mMetric2ConditionLinks[0],
+            getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0],
                                      &linkedConditionDimensionKey);
             if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
                     trueConditionDimensions.end()) {
-                for (auto& condIt : whatIt.second) {
-                    condIt.second->onConditionChanged(
-                            currentUnSlicedPartCondition, eventTime);
-                }
+                whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime);
             }
         }
     } else {
@@ -229,109 +252,15 @@
         if (currentUnSlicedPartCondition) {
             for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
                 HashableDimensionKey linkedConditionDimensionKey;
-                getDimensionForCondition(whatIt.first.getValues(),
-                                         mMetric2ConditionLinks[0],
+                getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0],
                                          &linkedConditionDimensionKey);
                 if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToTrue->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(true, eventTime);
-                    }
+                    whatIt.second->onConditionChanged(true, eventTime);
                 }
                 if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToFalse->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(false, eventTime);
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-// SlicedConditionChange optimization case 2:
-// 1. If combination condition, logical operation is AND, only one sliced child predicate.
-// 2. Has dimensions_in_condition and it equals to the output dimensions of the sliced predicate.
-void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(bool condition,
-                                                                   const int64_t eventTime) {
-    if (mMetric2ConditionLinks.size() > 1 || !mSameConditionDimensionsInTracker) {
-        return;
-    }
-
-    auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex);
-    auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex);
-
-    bool  currentUnSlicedPartCondition = true;
-    if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) {
-        ConditionState unslicedPartState =
-            mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex);
-        // When the unsliced part is still false, return directly.
-        if (mUnSlicedPartCondition == ConditionState::kFalse &&
-            unslicedPartState == ConditionState::kFalse) {
-            return;
-        }
-        mUnSlicedPartCondition = unslicedPartState;
-        currentUnSlicedPartCondition = mUnSlicedPartCondition > 0;
-    }
-
-    const std::set<HashableDimensionKey>* trueDimensionsToProcess = nullptr;
-    const std::set<HashableDimensionKey>* falseDimensionsToProcess = nullptr;
-
-    std::set<HashableDimensionKey> currentTrueConditionDimensions;
-    if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
-        (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
-        mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &currentTrueConditionDimensions);
-        trueDimensionsToProcess = &currentTrueConditionDimensions;
-    } else if (currentUnSlicedPartCondition) {
-        // Handles the condition change from the sliced predicate. If the unsliced condition state
-        // is not true, not need to do anything.
-        trueDimensionsToProcess = dimensionsChangedToTrue;
-        falseDimensionsToProcess = dimensionsChangedToFalse;
-    }
-
-    if (trueDimensionsToProcess == nullptr && falseDimensionsToProcess == nullptr) {
-        return;
-    }
-
-    for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        if (falseDimensionsToProcess != nullptr) {
-            for (const auto& changedDim : *falseDimensionsToProcess) {
-                auto condIt = whatIt.second.find(changedDim);
-                if (condIt != whatIt.second.end()) {
-                    condIt->second->onConditionChanged(false, eventTime);
-                }
-            }
-        }
-        if (trueDimensionsToProcess != nullptr) {
-            HashableDimensionKey linkedConditionDimensionKey;
-            if (!trueDimensionsToProcess->empty() && mMetric2ConditionLinks.size() == 1) {
-                getDimensionForCondition(whatIt.first.getValues(),
-                                         mMetric2ConditionLinks[0],
-                                         &linkedConditionDimensionKey);
-            }
-            for (auto& trueDim : *trueDimensionsToProcess) {
-                auto condIt = whatIt.second.find(trueDim);
-                if (condIt != whatIt.second.end()) {
-                    condIt->second->onConditionChanged(
-                            currentUnSlicedPartCondition, eventTime);
-                } else {
-                    if (mMetric2ConditionLinks.size() == 0 ||
-                        trueDim.contains(linkedConditionDimensionKey)) {
-                        if (!whatIt.second.empty()) {
-                            auto newEventKey = MetricDimensionKey(whatIt.first, trueDim);
-                            if (hitGuardRailLocked(newEventKey)) {
-                                continue;
-                            }
-                            unique_ptr<DurationTracker> newTracker =
-                                whatIt.second.begin()->second->clone(eventTime);
-                            if (newTracker != nullptr) {
-                                newTracker->setEventKey(newEventKey);
-                                newTracker->onConditionChanged(true, eventTime);
-                                whatIt.second[trueDim] = std::move(newTracker);
-                            }
-                        }
-                    }
+                    whatIt.second->onConditionChanged(false, eventTime);
                 }
             }
         }
@@ -341,85 +270,14 @@
 void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition,
         const int64_t eventTimeNs) {
     bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex);
-    if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker &&
-        mDimensionsInCondition.empty()) {
+    if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) {
         onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs);
         return;
     }
 
-    if (changeDimTrackable && mSameConditionDimensionsInTracker &&
-        mMetric2ConditionLinks.size() <= 1) {
-        onSlicedConditionMayChangeLocked_opt2(overallCondition, eventTimeNs);
-        return;
-    }
-
     // Now for each of the on-going event, check if the condition has changed for them.
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
-        }
-    }
-
-    if (mDimensionsInCondition.empty()) {
-        return;
-    }
-
-    if (mMetric2ConditionLinks.empty()) {
-        std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
-        mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
-                                          !mSameConditionDimensionsInTracker,
-                                          &conditionDimensionsKeySet);
-        for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (const auto& pair : whatIt.second) {
-                conditionDimensionsKeySet.erase(pair.first);
-            }
-        }
-        for (const auto& conditionDimension : conditionDimensionsKeySet) {
-            for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-                if (!whatIt.second.empty()) {
-                    auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension);
-                    if (hitGuardRailLocked(newEventKey)) {
-                        continue;
-                    }
-                    unique_ptr<DurationTracker> newTracker =
-                        whatIt.second.begin()->second->clone(eventTimeNs);
-                    if (newTracker != nullptr) {
-                        newTracker->setEventKey(MetricDimensionKey(newEventKey));
-                        newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs);
-                        whatIt.second[conditionDimension] = std::move(newTracker);
-                    }
-                }
-            }
-        }
-    } else {
-        for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            ConditionKey conditionKey;
-            for (const auto& link : mMetric2ConditionLinks) {
-                getDimensionForCondition(whatIt.first.getValues(), link,
-                                         &conditionKey[link.conditionId]);
-            }
-            std::unordered_set<HashableDimensionKey> conditionDimensionsKeys;
-            mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
-                           !mSameConditionDimensionsInTracker,
-                           !mHasLinksToAllConditionDimensionsInTracker,
-                           &conditionDimensionsKeys);
-
-            for (const auto& conditionDimension : conditionDimensionsKeys) {
-                if (!whatIt.second.empty() &&
-                    whatIt.second.find(conditionDimension) == whatIt.second.end()) {
-                    auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension);
-                    if (hitGuardRailLocked(newEventKey)) {
-                        continue;
-                    }
-                    auto newTracker = whatIt.second.begin()->second->clone(eventTimeNs);
-                    if (newTracker != nullptr) {
-                        newTracker->setEventKey(newEventKey);
-                        newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs);
-                        whatIt.second[conditionDimension] = std::move(newTracker);
-                    }
-                }
-            }
-        }
+        whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
     }
 }
 
@@ -453,18 +311,14 @@
         }
 
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     } else if (mIsActive) {
         flushIfNeededLocked(eventTimeNs);
         onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
     } else { // mConditionSliced == true && !mIsActive
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     }
 }
@@ -480,9 +334,7 @@
 
     flushIfNeededLocked(eventTime);
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onConditionChanged(conditionMet, eventTime);
-        }
+        whatIt.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
@@ -526,12 +378,6 @@
             writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
             protoOutput->end(dimenPathToken);
         }
-        if (!mDimensionsInCondition.empty()) {
-            uint64_t dimenPathToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION);
-            writeDimensionPathToProto(mDimensionsInCondition, protoOutput);
-            protoOutput->end(dimenPathToken);
-        }
     }
 
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
@@ -551,22 +397,16 @@
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
             protoOutput->end(dimensionToken);
-
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                uint64_t dimensionInConditionToken = protoOutput->start(
-                        FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
-                writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(),
-                                      str_set, protoOutput);
-                protoOutput->end(dimensionInConditionToken);
-            }
         } else {
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(),
-                                               FIELD_ID_DIMENSION_LEAF_IN_CONDITION,
-                                               str_set, protoOutput);
-            }
+        }
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
         }
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
@@ -614,19 +454,11 @@
                                                       const int64_t& nextBucketStartTimeNs) {
     for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
             whatIt != mCurrentSlicedDurationTrackerMap.end();) {
-        for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
-            if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
-                VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
-                     it->first.toString().c_str());
-                it = whatIt->second.erase(it);
-            } else {
-                ++it;
-            }
-        }
-        if (whatIt->second.empty()) {
+        if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+            VLOG("erase bucket for key %s", whatIt->first.toString().c_str());
             whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
         } else {
-            whatIt++;
+            ++whatIt;
         }
     }
     StatsdStats::getInstance().noteBucketCount(mMetricId);
@@ -642,34 +474,15 @@
             (unsigned long)mCurrentSlicedDurationTrackerMap.size());
     if (verbose) {
         for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (const auto& slice : whatIt.second) {
-                fprintf(out, "\t(what)%s\t(condition)%s\n", whatIt.first.toString().c_str(),
-                        slice.first.toString().c_str());
-                slice.second->dumpStates(out, verbose);
-            }
+            fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str());
+            whatIt.second->dumpStates(out, verbose);
         }
     }
 }
 
 bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
-    if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-        auto condIt = whatIt->second.find(newKey.getDimensionKeyInCondition());
-        if (condIt != whatIt->second.end()) {
-            return false;
-        }
-        if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-            size_t newTupleCount = whatIt->second.size() + 1;
-            StatsdStats::getInstance().noteMetricDimensionInConditionSize(
-                    mConfigKey, mMetricId, newTupleCount);
-            // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-            if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-                ALOGE("DurationMetric %lld dropping data for condition dimension key %s",
-                    (long long)mMetricId, newKey.getDimensionKeyInCondition().toString().c_str());
-                return true;
-            }
-        }
-    } else {
+    if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         // 1. Report the tuple count if the tuple count > soft limit
         if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
             size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -679,6 +492,7 @@
             if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
                 ALOGE("DurationMetric %lld dropping data for what dimension key %s",
                     (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str());
+                StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
                 return true;
             }
         }
@@ -690,46 +504,36 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-    const auto& condKey = eventKey.getDimensionKeyInCondition();
-
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
             return;
         }
-        mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
-    } else {
-        if (whatIt->second.find(condKey) == whatIt->second.end()) {
-            if (hitGuardRailLocked(eventKey)) {
-                return;
-            }
-            mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
-        }
+        mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey);
     }
 
-    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(condKey);
+    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
-        it->second->noteStart(whatKey, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
         return;
     }
 
     if (mInternalDimensions.empty()) {
-        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     } else {
         HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
         filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
-        it->second->noteStart(
-            dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     }
 
 }
 
 void DurationMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const MetricDimensionKey& eventKey,
-        const ConditionKey& conditionKeys, bool condition,
-        const LogEvent& event) {
+        const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
+        const map<int, HashableDimensionKey>& statePrimaryKeys) {
     ALOGW("Not used in duration tracker.");
 }
 
@@ -747,18 +551,49 @@
     // Handles Stopall events.
     if (matcherIndex == mStopAllIndex) {
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->noteStopAll(event.GetElapsedTimestampNs());
-            }
+            whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
         }
         return;
     }
 
-    HashableDimensionKey dimensionInWhat;
+    HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
     if (!mDimensionsInWhat.empty()) {
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
-    } else {
-       dimensionInWhat = DEFAULT_DIMENSION_KEY;
+    }
+
+    // Stores atom id to primary key pairs for each state atom that the metric is
+    // sliced by.
+    std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+    // For states with primary fields, use MetricStateLinks to get the primary
+    // field values from the log event. These values will form a primary key
+    // that will be used to query StateTracker for the correct state value.
+    for (const auto& stateLink : mMetric2StateLinks) {
+        getDimensionForState(event.getValues(), stateLink,
+                             &statePrimaryKeys[stateLink.stateAtomId]);
+    }
+
+    // For each sliced state, query StateTracker for the state value using
+    // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+    //
+    // Expected functionality: for any case where the MetricStateLinks are
+    // initialized incorrectly (ex. # of state links != # of primary fields, no
+    // links are provided for a state with primary fields, links are provided
+    // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+    // when queried using an incorrect key.
+    HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+    for (auto atomId : mSlicedStateAtoms) {
+        FieldValue value;
+        if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+            // found a primary key for this state, query using the key
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+        } else {
+            // if no MetricStateLinks exist for this state atom,
+            // query using the default dimension key (empty HashableDimensionKey)
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+        }
+        mapStateValue(atomId, &value);
+        stateValuesKey.addValue(value);
     }
 
     // Handles Stop events.
@@ -766,9 +601,7 @@
         if (mUseWhatDimensionAsInternalDimension) {
             auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
             if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                for (const auto& condIt : whatIt->second) {
-                    condIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
-                }
+                whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
             }
             return;
         }
@@ -780,62 +613,31 @@
 
         auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
         if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-            for (const auto& condIt : whatIt->second) {
-                condIt.second->noteStop(
-                    internalDimensionKey, event.GetElapsedTimestampNs(), false);
-            }
+            whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
         }
         return;
     }
 
     bool condition;
     ConditionKey conditionKey;
-    std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
     if (mConditionSliced) {
         for (const auto& link : mMetric2ConditionLinks) {
             getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
         }
 
         auto conditionState =
-            mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
-                           !mSameConditionDimensionsInTracker,
-                           !mHasLinksToAllConditionDimensionsInTracker,
-                           &dimensionKeysInCondition);
+            mWizard->query(mConditionTrackerIndex, conditionKey,
+                           !mHasLinksToAllConditionDimensionsInTracker);
         condition = conditionState == ConditionState::kTrue;
-        if (mDimensionsInCondition.empty() && condition) {
-            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
-        }
     } else {
         // TODO: The unknown condition state is not handled here, we should fix it.
         condition = mCondition == ConditionState::kTrue;
-        if (condition) {
-            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
-        }
     }
 
     condition = condition && mIsActive;
 
-    if (dimensionKeysInCondition.empty()) {
-        handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY),
-                         conditionKey, condition, event);
-    } else {
-        auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
-        // If the what dimension is already there, we should update all the trackers even
-        // the condition is false.
-        if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-            for (const auto& condIt : whatIt->second) {
-                const bool cond = dimensionKeysInCondition.find(condIt.first) !=
-                        dimensionKeysInCondition.end() && condition;
-                handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first),
-                    conditionKey, cond, event);
-                dimensionKeysInCondition.erase(condIt.first);
-            }
-        }
-        for (const auto& conditionDimension : dimensionKeysInCondition) {
-            handleStartEvent(MetricDimensionKey(dimensionInWhat, conditionDimension), conditionKey,
-                             condition, event);
-        }
-    }
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+                     event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 56c9fd6..bfe1010 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -38,24 +38,33 @@
 
 class DurationMetricProducer : public MetricProducer {
 public:
-    DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric,
-                           const int conditionIndex, const size_t startIndex,
-                           const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
-                           const sp<ConditionWizard>& wizard,
-                           const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs);
+    DurationMetricProducer(
+            const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache, const size_t startIndex,
+            const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
+            const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions,
+            const int64_t timeBaseNs, const int64_t startTimeNs,
+            const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
+            const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~DurationMetricProducer();
 
     sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                          const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) override;
+
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
 
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKeys, bool condition,
-            const LogEvent& event) override;
+            const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
 
 private:
     void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
@@ -127,13 +136,12 @@
     std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
 
     // The duration trackers in the current bucket.
-    std::unordered_map<HashableDimensionKey,
-        std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
+    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDurationTrackerMap;
 
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
-        const MetricDimensionKey& eventKey) const;
+            const MetricDimensionKey& eventKey) const;
 
     // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
@@ -146,12 +154,14 @@
     FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState);
-    FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade);
-    FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket);
-    FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade);
-    FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
     FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket);
+
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket,
+                TestSumDurationWithSplitInFollowingBucket);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 96133bd..dc0036a 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -36,6 +36,7 @@
 using std::string;
 using std::unordered_map;
 using std::vector;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -51,11 +52,16 @@
 const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1;
 const int FIELD_ID_ATOMS = 2;
 
-EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric,
-                                         const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard,
-                                         const int64_t startTimeNs)
-    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
+EventMetricProducer::EventMetricProducer(
+        const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const int64_t startTimeNs,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard,
+                     eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
             Metric2Condition mc;
@@ -138,16 +144,15 @@
 
 void EventMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const MetricDimensionKey& eventKey,
-        const ConditionKey& conditionKey, bool condition,
-        const LogEvent& event) {
+        const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+        const map<int, HashableDimensionKey>& statePrimaryKeys) {
     if (!condition) {
         return;
     }
 
     uint64_t wrapperToken =
             mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
-    const int64_t elapsedTimeNs = truncateTimestampIfNecessary(
-            event.GetTagId(), event.GetElapsedTimestampNs());
+    const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event);
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs);
 
     uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS);
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 74e6bc8..bfb2de3 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -33,17 +33,23 @@
 
 class EventMetricProducer : public MetricProducer {
 public:
-    EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int64_t startTimeNs);
+    EventMetricProducer(
+            const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+            const int64_t startTimeNs,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~EventMetricProducer();
 
 private:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKey, bool condition,
-            const LogEvent& event) override;
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
 
     void onDumpReportLocked(const int64_t dumpTimeNs,
                             const bool include_current_partial_bucket,
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 4f437d1..020f4b6 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -46,19 +46,21 @@
 const int FIELD_ID_TIME_BASE = 9;
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
-const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
 const int FIELD_ID_IS_ACTIVE = 14;
 // for GaugeMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
+// for SkippedBuckets
 const int FIELD_ID_SKIPPED_START_MILLIS = 3;
 const int FIELD_ID_SKIPPED_END_MILLIS = 4;
+const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
+// for DumpEvent Proto
+const int FIELD_ID_BUCKET_DROP_REASON = 1;
+const int FIELD_ID_DROP_TIME = 2;
 // for GaugeMetricData
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
-const int FIELD_ID_DIMENSION_IN_CONDITION = 2;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
-const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5;
 // for GaugeBucketInfo
 const int FIELD_ID_ATOM = 3;
 const int FIELD_ID_ELAPSED_ATOM_TIMESTAMP = 4;
@@ -68,11 +70,15 @@
 
 GaugeMetricProducer::GaugeMetricProducer(
         const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex,
-        const sp<ConditionWizard>& wizard, const int whatMatcherIndex,
-        const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int triggerAtomId,
-        const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
-        const sp<StatsPullerManager>& pullerManager)
-    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
+        const int pullTagId, const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
+        const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
+                     eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{},
+                     /*stateGroupMap=*/{}),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
@@ -113,10 +119,6 @@
         mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
-    if (metric.has_dimensions_in_condition()) {
-        translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
-    }
-
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
             Metric2Condition mc;
@@ -125,19 +127,18 @@
             translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
             mMetric2ConditionLinks.push_back(mc);
         }
+        mConditionSliced = true;
     }
-    mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) ||
-            HasPositionALL(metric.dimensions_in_condition());
+    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
 
     flushIfNeededLocked(startTimeNs);
     // Kicks off the puller immediately.
     if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
-        mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(),
+        mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(),
                                          mBucketSizeNs);
     }
 
-    // Adjust start for partial bucket
+    // Adjust start for partial first bucket and then pull if needed
     mCurrentBucketStartTimeNs = startTimeNs;
 
     VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d",
@@ -148,7 +149,7 @@
 GaugeMetricProducer::~GaugeMetricProducer() {
     VLOG("~GaugeMetricProducer() called");
     if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
-        mPullerManager->UnRegisterReceiver(mPullTagId, this);
+        mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this);
     }
 }
 
@@ -162,10 +163,9 @@
             (unsigned long)mCurrentSlicedBucket->size());
     if (verbose) {
         for (const auto& it : *mCurrentSlicedBucket) {
-            fprintf(out, "\t(what)%s\t(condition)%s  %d atoms\n",
-                it.first.getDimensionKeyInWhat().toString().c_str(),
-                it.first.getDimensionKeyInCondition().toString().c_str(),
-                (int)it.second.size());
+            fprintf(out, "\t(what)%s\t(states)%s  %d atoms\n",
+                    it.first.getDimensionKeyInWhat().toString().c_str(),
+                    it.first.getStateValuesKey().toString().c_str(), (int)it.second.size());
         }
     }
 }
@@ -192,7 +192,7 @@
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
 
-    if (mPastBuckets.empty()) {
+    if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
         return;
     }
 
@@ -207,23 +207,25 @@
             writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
             protoOutput->end(dimenPathToken);
         }
-        if (!mDimensionsInCondition.empty()) {
-            uint64_t dimenPathToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION);
-            writeDimensionPathToProto(mDimensionsInCondition, protoOutput);
-            protoOutput->end(dimenPathToken);
-        }
     }
 
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
 
-    for (const auto& pair : mSkippedBuckets) {
+    for (const auto& skippedBucket : mSkippedBuckets) {
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
         protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
-                           (long long)(NanoToMillis(pair.first)));
+                           (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
         protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
-                           (long long)(NanoToMillis(pair.second)));
+                           (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
+
+        for (const auto& dropEvent : skippedBucket.dropEvents) {
+            uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                         FIELD_ID_SKIPPED_DROP_EVENT);
+            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
+            protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs)));
+            protoOutput->end(dropEventToken);
+        }
         protoOutput->end(wrapperToken);
     }
 
@@ -240,22 +242,9 @@
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
             protoOutput->end(dimensionToken);
-
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                uint64_t dimensionInConditionToken = protoOutput->start(
-                        FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
-                writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(),
-                                      str_set, protoOutput);
-                protoOutput->end(dimensionInConditionToken);
-            }
         } else {
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(),
-                                               FIELD_ID_DIMENSION_LEAF_IN_CONDITION,
-                                               str_set, protoOutput);
-            }
         }
 
         // Then fill bucket_info (GaugeBucketInfo).
@@ -282,11 +271,9 @@
                     protoOutput->end(atomsToken);
                 }
                 for (const auto& atom : bucket.mGaugeAtoms) {
-                    const int64_t elapsedTimestampNs =
-                            truncateTimestampIfNecessary(mAtomId, atom.mElapsedTimestamps);
-                    protoOutput->write(
-                        FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_ELAPSED_ATOM_TIMESTAMP,
-                        (long long)elapsedTimestampNs);
+                    protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED |
+                                               FIELD_ID_ELAPSED_ATOM_TIMESTAMP,
+                                       (long long)atom.mElapsedTimestampNs);
                 }
             }
             protoOutput->end(bucketInfoToken);
@@ -335,18 +322,17 @@
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
-    if (!mPullerManager->Pull(mPullTagId, &allData)) {
+    if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) {
         ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs);
         return;
     }
     const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs;
+    StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
     if (pullDelayNs > mMaxPullDelayNs) {
         ALOGE("Pull finish too late for atom %d", mPullTagId);
         StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
-        StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
         return;
     }
-    StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
     for (const auto& data : allData) {
         LogEvent localCopy = data->makeCopy();
         localCopy.setElapsedTimestampNs(timestampNs);
@@ -429,6 +415,13 @@
     if (!pullSuccess || allData.size() == 0) {
         return;
     }
+    const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs;
+    StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
+    if (pullDelayNs > mMaxPullDelayNs) {
+        ALOGE("Pull finish too late for atom %d", mPullTagId);
+        StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
+        return;
+    }
     for (const auto& data : allData) {
         if (mEventMatcherWizard->matchLogEvent(
                 *data, mWhatMatcherIndex) == MatchingState::kMatched) {
@@ -449,6 +442,7 @@
         if (newTupleCount > mDimensionHardLimit) {
             ALOGE("GaugeMetric %lld dropping data for dimension key %s",
                 (long long)mMetricId, newKey.toString().c_str());
+            StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
             return true;
         }
     }
@@ -458,8 +452,8 @@
 
 void GaugeMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const MetricDimensionKey& eventKey,
-        const ConditionKey& conditionKey, bool condition,
-        const LogEvent& event) {
+        const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+        const map<int, HashableDimensionKey>& statePrimaryKeys) {
     if (condition == false) {
         return;
     }
@@ -488,7 +482,9 @@
     if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) {
         return;
     }
-    GaugeAtom gaugeAtom(getGaugeFields(event), eventTimeNs);
+
+    const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event);
+    GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs);
     (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom);
     // Anomaly detection on gauge metric only works when there is one numeric
     // field specified.
@@ -558,16 +554,16 @@
 void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                                    const int64_t& nextBucketStartTimeNs) {
     int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
+    int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs;
 
     GaugeBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
-    if (eventTimeNs < fullBucketEndTimeNs) {
-        info.mBucketEndNs = eventTimeNs;
-    } else {
-        info.mBucketEndNs = fullBucketEndTimeNs;
-    }
+    info.mBucketEndNs = bucketEndTime;
 
-    if (info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs) {
+    // Add bucket to mPastBuckets if bucket is large enough.
+    // Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets.
+    bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
+    if (isBucketLargeEnough) {
         for (const auto& slice : *mCurrentSlicedBucket) {
             info.mGaugeAtoms = slice.second;
             auto& bucketList = mPastBuckets[slice.first];
@@ -576,7 +572,13 @@
                  slice.first.toString().c_str());
         }
     } else {
-        mSkippedBuckets.emplace_back(info.mBucketStartNs, info.mBucketEndNs);
+        mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
+        mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
+        if (!maxDropEventsReached()) {
+            mCurrentSkippedBucket.dropEvents.emplace_back(
+                    buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
+        }
+        mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
     }
 
     // If we have anomaly trackers, we need to update the partial bucket values.
@@ -595,6 +597,7 @@
     StatsdStats::getInstance().noteBucketCount(mMetricId);
     mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
     mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
+    mCurrentSkippedBucket.reset();
 }
 
 size_t GaugeMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index a612adf..2fc772b 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -35,10 +35,10 @@
 
 struct GaugeAtom {
     GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs)
-        : mFields(fields), mElapsedTimestamps(elapsedTimeNs) {
+        : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) {
     }
     std::shared_ptr<vector<FieldValue>> mFields;
-    int64_t mElapsedTimestamps;
+    int64_t mElapsedTimestampNs;
 };
 
 struct GaugeBucket {
@@ -56,13 +56,16 @@
 // producer always reports the guage at the earliest time of the bucket when the condition is met.
 class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
-                        const int whatMatcherIndex,
-                        const sp<EventMatcherWizard>& matcherWizard,
-                        const int pullTagId, const int triggerAtomId, const int atomId,
-                        const int64_t timeBaseNs, const int64_t startTimeNs,
-                        const sp<StatsPullerManager>& pullerManager);
+    GaugeMetricProducer(
+            const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache,
+            const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+            const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+            const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
+            const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {});
 
     virtual ~GaugeMetricProducer();
 
@@ -71,18 +74,23 @@
                       bool pullSuccess, int64_t originalPullTimeNs) override;
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override {
+    void notifyAppUpgrade(const int64_t& eventTimeNs) override {
         std::lock_guard<std::mutex> lock(mMutex);
 
         if (!mSplitBucketForAppUpgrade) {
             return;
         }
-        if (eventTimeNs > getCurrentBucketEndTimeNs()) {
-            // Flush full buckets on the normal path up to the latest bucket boundary.
-            flushIfNeededLocked(eventTimeNs);
+        flushLocked(eventTimeNs);
+        if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
+            pullAndMatchEventsLocked(eventTimeNs);
         }
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
+
+    // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
+    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        flushLocked(eventTimeNs);
         if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
             pullAndMatchEventsLocked(eventTimeNs);
         }
@@ -91,8 +99,8 @@
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKey, bool condition,
-            const LogEvent& event) override;
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
 
 private:
     void onDumpReportLocked(const int64_t dumpTimeNs,
@@ -156,9 +164,6 @@
     // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
     std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
 
-    // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped.
-    std::list<std::pair<int64_t, int64_t>> mSkippedBuckets;
-
     const int64_t mMinBucketSizeNs;
 
     // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
@@ -191,13 +196,14 @@
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
-    FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade);
-    FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection);
     FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
     FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
     FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
+
+    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
+    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 36434eb..fe143e4 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -16,8 +16,12 @@
 
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
+
 #include "MetricProducer.h"
 
+#include "../guardrail/StatsdStats.h"
+#include "state/StateTracker.h"
+
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_ENUM;
 using android::util::FIELD_TYPE_INT32;
@@ -39,6 +43,34 @@
 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2;
 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3;
 
+MetricProducer::MetricProducer(
+        const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
+        const int conditionIndex, const vector<ConditionState>& initialConditionCache,
+        const sp<ConditionWizard>& wizard,
+        const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
+        const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : mMetricId(metricId),
+      mConfigKey(key),
+      mTimeBaseNs(timeBaseNs),
+      mCurrentBucketStartTimeNs(timeBaseNs),
+      mCurrentBucketNum(0),
+      mCondition(initialCondition(conditionIndex, initialConditionCache)),
+      mConditionTrackerIndex(conditionIndex),
+      mConditionSliced(false),
+      mWizard(wizard),
+      mContainANYPositionInDimensionsInWhat(false),
+      mSliceByPositionALL(false),
+      mHasLinksToAllConditionDimensionsInTracker(false),
+      mEventActivationMap(eventActivationMap),
+      mEventDeactivationMap(eventDeactivationMap),
+      mIsActive(mEventActivationMap.empty()),
+      mSlicedStateAtoms(slicedStateAtoms),
+      mStateGroupMap(stateGroupMap) {
+}
+
 void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
     if (!mIsActive) {
         return;
@@ -51,38 +83,59 @@
 
     bool condition;
     ConditionKey conditionKey;
-    std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
     if (mConditionSliced) {
         for (const auto& link : mMetric2ConditionLinks) {
             getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
         }
         auto conditionState =
-            mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
-                           !mSameConditionDimensionsInTracker,
-                           !mHasLinksToAllConditionDimensionsInTracker,
-                           &dimensionKeysInCondition);
+            mWizard->query(mConditionTrackerIndex, conditionKey,
+                           !mHasLinksToAllConditionDimensionsInTracker);
         condition = (conditionState == ConditionState::kTrue);
     } else {
         // TODO: The unknown condition state is not handled here, we should fix it.
         condition = mCondition == ConditionState::kTrue;
     }
 
-    if (mDimensionsInCondition.empty() && condition) {
-        dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+    // Stores atom id to primary key pairs for each state atom that the metric is
+    // sliced by.
+    std::map<int32_t, HashableDimensionKey> statePrimaryKeys;
+
+    // For states with primary fields, use MetricStateLinks to get the primary
+    // field values from the log event. These values will form a primary key
+    // that will be used to query StateTracker for the correct state value.
+    for (const auto& stateLink : mMetric2StateLinks) {
+        getDimensionForState(event.getValues(), stateLink,
+                             &statePrimaryKeys[stateLink.stateAtomId]);
+    }
+
+    // For each sliced state, query StateTracker for the state value using
+    // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+    //
+    // Expected functionality: for any case where the MetricStateLinks are
+    // initialized incorrectly (ex. # of state links != # of primary fields, no
+    // links are provided for a state with primary fields, links are provided
+    // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+    // when queried using an incorrect key.
+    HashableDimensionKey stateValuesKey;
+    for (auto atomId : mSlicedStateAtoms) {
+        FieldValue value;
+        if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+            // found a primary key for this state, query using the key
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+        } else {
+            // if no MetricStateLinks exist for this state atom,
+            // query using the default dimension key (empty HashableDimensionKey)
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+        }
+        mapStateValue(atomId, &value);
+        stateValuesKey.addValue(value);
     }
 
     HashableDimensionKey dimensionInWhat;
     filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
-    MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
-    for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
-        metricKey.setDimensionKeyInCondition(conditionDimensionKey);
-        onMatchedLogEventInternalLocked(
-                matcherIndex, metricKey, conditionKey, condition, event);
-    }
-    if (dimensionKeysInCondition.empty()) {
-        onMatchedLogEventInternalLocked(
-                matcherIndex, metricKey, conditionKey, condition, event);
-    }
+    MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey);
+    onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event,
+                                    statePrimaryKeys);
 }
 
 bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
@@ -110,24 +163,6 @@
     }
 }
 
-void MetricProducer::addActivation(int activationTrackerIndex, const ActivationType& activationType,
-        int64_t ttl_seconds, int deactivationTrackerIndex) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    // When a metric producer does not depend on any activation, its mIsActive is true.
-    // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not
-    // change.
-    if  (mEventActivationMap.empty()) {
-        mIsActive = false;
-    }
-    std::shared_ptr<Activation> activation =
-            std::make_shared<Activation>(activationType, ttl_seconds * NS_PER_SEC);
-    mEventActivationMap.emplace(activationTrackerIndex, activation);
-    if (-1 != deactivationTrackerIndex) {
-        auto& deactivationList = mEventDeactivationMap[deactivationTrackerIndex];
-        deactivationList.push_back(activation);
-    }
-}
-
 void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) {
     auto it = mEventActivationMap.find(activationTrackerIndex);
     if (it == mEventActivationMap.end()) {
@@ -231,6 +266,56 @@
     }
 }
 
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                                     FieldValue* value) {
+    if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
+        value->mValue = Value(StateTracker::kStateUnknown);
+        value->mField.setTag(atomId);
+        ALOGW("StateTracker not found for state atom %d", atomId);
+        return;
+    }
+}
+
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
+    // check if there is a state map for this atom
+    auto atomIt = mStateGroupMap.find(atomId);
+    if (atomIt == mStateGroupMap.end()) {
+        return;
+    }
+    auto valueIt = atomIt->second.find(value->mValue.int_value);
+    if (valueIt == atomIt->second.end()) {
+        // state map exists, but value was not put in a state group
+        // so set mValue to kStateUnknown
+        // TODO(tsaichristine): handle incomplete state maps
+        value->mValue.setInt(StateTracker::kStateUnknown);
+    } else {
+        // set mValue to group_id
+        value->mValue.setLong(valueIt->second);
+    }
+}
+
+HashableDimensionKey MetricProducer::getUnknownStateKey() {
+    HashableDimensionKey stateKey;
+    for (auto atom : mSlicedStateAtoms) {
+        FieldValue fieldValue;
+        fieldValue.mField.setTag(atom);
+        fieldValue.mValue.setInt(StateTracker::kStateUnknown);
+        stateKey.addValue(fieldValue);
+    }
+    return stateKey;
+}
+
+DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) {
+    DropEvent event;
+    event.reason = reason;
+    event.dropTimeNs = dropTimeNs;
+    return event;
+}
+
+bool MetricProducer::maxDropEventsReached() {
+    return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index c77bc01..be4cd67 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -28,6 +28,8 @@
 #include "config/ConfigKey.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
+#include "state/StateListener.h"
+#include "state/StateManager.h"
 
 namespace android {
 namespace os {
@@ -67,61 +69,101 @@
     NO_TIME_CONSTRAINTS = 2
 };
 
+// Keep this in sync with BucketDropReason enum in stats_log.proto
+enum BucketDropReason {
+    // For ValueMetric, a bucket is dropped during a dump report request iff
+    // current bucket should be included, a pull is needed (pulled metric and
+    // condition is true), and we are under fast time constraints.
+    DUMP_REPORT_REQUESTED = 1,
+    EVENT_IN_WRONG_BUCKET = 2,
+    CONDITION_UNKNOWN = 3,
+    PULL_FAILED = 4,
+    PULL_DELAYED = 5,
+    DIMENSION_GUARDRAIL_REACHED = 6,
+    MULTIPLE_BUCKETS_SKIPPED = 7,
+    // Not an invalid bucket case, but the bucket is dropped.
+    BUCKET_TOO_SMALL = 8,
+    // Not an invalid bucket case, but the bucket is skipped.
+    NO_DATA = 9
+};
+
+struct Activation {
+    Activation(const ActivationType& activationType, const int64_t ttlNs)
+        : ttl_ns(ttlNs),
+          start_ns(0),
+          state(ActivationState::kNotActive),
+          activationType(activationType) {}
+
+    const int64_t ttl_ns;
+    int64_t start_ns;
+    ActivationState state;
+    const ActivationType activationType;
+};
+
+struct DropEvent {
+    // Reason for dropping the bucket and/or marking the bucket invalid.
+    BucketDropReason reason;
+    // The timestamp of the drop event.
+    int64_t dropTimeNs;
+};
+
+struct SkippedBucket {
+    // Start time of the dropped bucket.
+    int64_t bucketStartTimeNs;
+    // End time of the dropped bucket.
+    int64_t bucketEndTimeNs;
+    // List of events that invalidated this bucket.
+    std::vector<DropEvent> dropEvents;
+
+    void reset() {
+        bucketStartTimeNs = 0;
+        bucketEndTimeNs = 0;
+        dropEvents.clear();
+    }
+};
+
 // A MetricProducer is responsible for compute one single metrics, creating stats log report, and
 // writing the report to dropbox. MetricProducers should respond to package changes as required in
 // PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
 // be a no-op.
-class MetricProducer : public virtual android::RefBase {
+class MetricProducer : public virtual android::RefBase, public virtual StateListener {
 public:
     MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
-                   const int conditionIndex, const sp<ConditionWizard>& wizard)
-        : mMetricId(metricId),
-          mConfigKey(key),
-          mTimeBaseNs(timeBaseNs),
-          mCurrentBucketStartTimeNs(timeBaseNs),
-          mCurrentBucketNum(0),
-          mCondition(initialCondition(conditionIndex)),
-          mConditionSliced(false),
-          mWizard(wizard),
-          mConditionTrackerIndex(conditionIndex),
-          mContainANYPositionInDimensionsInWhat(false),
-          mSliceByPositionALL(false),
-          mSameConditionDimensionsInTracker(false),
-          mHasLinksToAllConditionDimensionsInTracker(false),
-          mIsActive(true) {
-    }
+                   const int conditionIndex, const vector<ConditionState>& initialConditionCache,
+                   const sp<ConditionWizard>& wizard,
+                   const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
+                   const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                           eventDeactivationMap,
+                   const vector<int>& slicedStateAtoms,
+                   const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap);
 
     virtual ~MetricProducer(){};
 
-    ConditionState initialCondition(const int conditionIndex) const {
-        return conditionIndex >= 0 ? ConditionState::kUnknown : ConditionState::kTrue;
+    ConditionState initialCondition(const int conditionIndex,
+                                    const vector<ConditionState>& initialConditionCache) const {
+        return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue;
     }
 
     /**
-     * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we
-     * first call the standard flushing code to flush up to the latest full bucket. Then we call
-     * the flush again when the end timestamp is forced to be now, and then after flushing, update
-     * the start timestamp to be now.
+     * Force a partial bucket split on app upgrade
      */
-    virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) {
+    virtual void notifyAppUpgrade(const int64_t& eventTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
-
-        if (eventTimeNs > getCurrentBucketEndTimeNs()) {
-            // Flush full buckets on the normal path up to the latest bucket boundary.
-            flushIfNeededLocked(eventTimeNs);
-        }
-        // Now flush a partial bucket.
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-        // Don't update the current bucket number so that the anomaly tracker knows this bucket
-        // is a partial bucket and can merge it with the previous bucket.
+        flushLocked(eventTimeNs);
     };
 
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) {
+    void notifyAppRemoved(const int64_t& eventTimeNs) {
         // Force buckets to split on removal also.
-        notifyAppUpgrade(eventTimeNs, apk, uid, 0);
+        notifyAppUpgrade(eventTimeNs);
     };
 
+    /**
+     * Force a partial bucket split on boot complete.
+     */
+    virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        flushLocked(eventTimeNs);
+    }
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
@@ -143,6 +185,10 @@
         return mConditionSliced;
     };
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState){};
+
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
     void onDumpReport(const int64_t dumpTimeNs,
@@ -161,9 +207,9 @@
         return clearPastBucketsLocked(dumpTimeNs);
     }
 
-    void dumpStates(FILE* out, bool verbose) const {
+    void prepareFirstBucket() {
         std::lock_guard<std::mutex> lock(mMutex);
-        dumpStatesLocked(out, verbose);
+        prepareFirstBucketLocked();
     }
 
     // Returns the memory in bytes currently used to store this metric's data. Does not change
@@ -173,34 +219,9 @@
         return byteSizeLocked();
     }
 
-    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
-    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
-                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+    void dumpStates(FILE* out, bool verbose) const {
         std::lock_guard<std::mutex> lock(mMutex);
-        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
-        if (anomalyTracker != nullptr) {
-            mAnomalyTrackers.push_back(anomalyTracker);
-        }
-        return anomalyTracker;
-    }
-
-    int64_t getBuckeSizeInNs() const {
-        std::lock_guard<std::mutex> lock(mMutex);
-        return mBucketSizeNs;
-    }
-
-    // Only needed for unit-testing to override guardrail.
-    void setBucketSize(int64_t bucketSize) {
-        mBucketSizeNs = bucketSize;
-    }
-
-    inline const int64_t& getMetricId() const {
-        return mMetricId;
-    }
-
-    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        loadActiveMetricLocked(activeMetric, currentTimeNs);
+        dumpStatesLocked(out, verbose);
     }
 
     // Let MetricProducer drop in-memory data to save memory.
@@ -212,9 +233,9 @@
         dropDataLocked(dropTimeNs);
     }
 
-    // For test only.
-    inline int64_t getCurrentBucketNum() const {
-        return mCurrentBucketNum;
+    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        loadActiveMetricLocked(activeMetric, currentTimeNs);
     }
 
     void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) {
@@ -232,59 +253,49 @@
         return isActiveLocked();
     }
 
-    void addActivation(int activationTrackerIndex, const ActivationType& activationType,
-            int64_t ttl_seconds, int deactivationTrackerIndex = -1);
-
-    void prepareFirstBucket() {
-        std::lock_guard<std::mutex> lock(mMutex);
-        prepareFirstBucketLocked();
-    }
-
     void flushIfExpire(int64_t elapsedTimestampNs);
 
     void writeActiveMetricToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
-protected:
-    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
-    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
-                                                  const int64_t eventTime) = 0;
-    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
-                                    const bool include_current_partial_bucket,
-                                    const bool erase_data,
-                                    const DumpLatency dumpLatency,
-                                    std::set<string> *str_set,
-                                    android::util::ProtoOutputStream* protoOutput) = 0;
-    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
-    virtual size_t byteSizeLocked() const = 0;
-    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
 
-    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
-
-    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
-    void cancelEventActivationLocked(int deactivationTrackerIndex);
-
-    inline bool isActiveLocked() const {
-        return mIsActive;
+    // Start: getters/setters
+    inline const int64_t& getMetricId() const {
+        return mMetricId;
     }
 
-    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    // For test only.
+    inline int64_t getCurrentBucketNum() const {
+        return mCurrentBucketNum;
+    }
 
-    virtual void prepareFirstBucketLocked() {};
+    int64_t getBucketSizeInNs() const {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mBucketSizeNs;
+    }
+
+    inline const std::vector<int> getSlicedStateAtoms() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mSlicedStateAtoms;
+    }
+
+    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
+    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
+                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
+        if (anomalyTracker != nullptr) {
+            mAnomalyTrackers.push_back(anomalyTracker);
+        }
+        return anomalyTracker;
+    }
+    // End: getters/setters
+protected:
     /**
-     * Flushes the current bucket if the eventTime is after the current bucket's end time. This will
-       also flush the current partial bucket in memory.
+     * Flushes the current bucket if the eventTime is after the current bucket's end time.
      */
     virtual void flushIfNeededLocked(const int64_t& eventTime){};
 
     /**
-     * Flushes all the data including the current partial bucket.
-     */
-    virtual void flushLocked(const int64_t& eventTimeNs) {
-        flushIfNeededLocked(eventTimeNs);
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-    };
-
-    /**
      * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
      * we need to be able to flush the current buckets on demand (ie, end the current bucket and
      * start new bucket). If this function is called when eventTimeNs is greater than the current
@@ -297,12 +308,66 @@
     virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                           const int64_t& nextBucketStartTimeNs) {};
 
+    /**
+     * Flushes all the data including the current partial bucket.
+     */
+    virtual void flushLocked(const int64_t& eventTimeNs) {
+        flushIfNeededLocked(eventTimeNs);
+        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
+
+    /*
+     * Individual metrics can implement their own business logic here. All pre-processing is done.
+     *
+     * [matcherIndex]: the index of the matcher which matched this event. This is interesting to
+     *                 DurationMetric, because it has start/stop/stop_all 3 matchers.
+     * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
+     *             dimensions, it will be DEFAULT_DIMENSION_KEY
+     * [conditionKey]: the keys of conditions which should be used to query the condition for this
+     *                 target event (from MetricConditionLink). This is passed to individual metrics
+     *                 because DurationMetric needs it to be cached.
+     * [condition]: whether condition is met. If condition is sliced, this is the result coming from
+     *              query with ConditionWizard; If condition is not sliced, this is the
+     *              nonSlicedCondition.
+     * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
+     */
+    virtual void onMatchedLogEventInternalLocked(
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const map<int, HashableDimensionKey>& statePrimaryKeys) = 0;
+
+    // Consume the parsed stats log entry that already matched the "what" of the metric.
+    virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
+    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
+                                                  const int64_t eventTime) = 0;
+    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
+                                    const bool include_current_partial_bucket,
+                                    const bool erase_data,
+                                    const DumpLatency dumpLatency,
+                                    std::set<string> *str_set,
+                                    android::util::ProtoOutputStream* protoOutput) = 0;
+    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
+    virtual void prepareFirstBucketLocked(){};
+    virtual size_t byteSizeLocked() const = 0;
+    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
+    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
+    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
+    void cancelEventActivationLocked(int deactivationTrackerIndex);
+
+    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
+
     virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
         if (!mIsActive) {
             flushLocked(eventTimeNs);
         }
     }
 
+    inline bool isActiveLocked() const {
+        return mIsActive;
+    }
+
     // Convenience to compute the current bucket's end time, which is always aligned with the
     // start time of the metric.
     int64_t getCurrentBucketEndTimeNs() const {
@@ -313,7 +378,25 @@
         return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
     }
 
-    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
+    // Query StateManager for original state value using the queryKey.
+    // The field and value are output.
+    void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                         FieldValue* value);
+
+    // If a state map exists for the given atom, replace the original state
+    // value with the group id mapped to the value.
+    // If no state map exists, keep the original state value.
+    void mapStateValue(const int32_t atomId, FieldValue* value);
+
+    // Returns a HashableDimensionKey with unknown state value for each state
+    // atom.
+    HashableDimensionKey getUnknownStateKey();
+
+    DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
+
+    // Returns true if the number of drop events in the current bucket has
+    // exceeded the maximum number allowed, which is currently capped at 10.
+    bool maxDropEventsReached();
 
     const int64_t mMetricId;
 
@@ -335,21 +418,17 @@
 
     ConditionState mCondition;
 
+    int mConditionTrackerIndex;
+
     bool mConditionSliced;
 
     sp<ConditionWizard> mWizard;
 
-    int mConditionTrackerIndex;
-
-    vector<Matcher> mDimensionsInWhat;       // The dimensions_in_what defined in statsd_config
-    vector<Matcher> mDimensionsInCondition;  // The dimensions_in_condition defined in statsd_config
-
     bool mContainANYPositionInDimensionsInWhat;
+
     bool mSliceByPositionALL;
 
-    // True iff the condition dimensions equal to the sliced dimensions in the simple condition
-    // tracker. This field is always false for combinational condition trackers.
-    bool mSameConditionDimensionsInTracker;
+    vector<Matcher> mDimensionsInWhat;  // The dimensions_in_what defined in statsd_config
 
     // True iff the metric to condition links cover all dimension fields in the condition tracker.
     // This field is always false for combinational condition trackers.
@@ -359,43 +438,8 @@
 
     std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
 
-    /*
-     * Individual metrics can implement their own business logic here. All pre-processing is done.
-     *
-     * [matcherIndex]: the index of the matcher which matched this event. This is interesting to
-     *                 DurationMetric, because it has start/stop/stop_all 3 matchers.
-     * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
-     *             dimensions, it will be DEFAULT_DIMENSION_KEY
-     * [conditionKey]: the keys of conditions which should be used to query the condition for this
-     *                 target event (from MetricConditionLink). This is passed to individual metrics
-     *                 because DurationMetric needs it to be cached.
-     * [condition]: whether condition is met. If condition is sliced, this is the result coming from
-     *              query with ConditionWizard; If condition is not sliced, this is the
-     *              nonSlicedCondition.
-     * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
-     */
-    virtual void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKey, bool condition,
-            const LogEvent& event) = 0;
-
-    // Consume the parsed stats log entry that already matched the "what" of the metric.
-    virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
-
     mutable std::mutex mMutex;
 
-    struct Activation {
-        Activation(const ActivationType& activationType, const int64_t ttlNs)
-            : ttl_ns(ttlNs),
-              start_ns(0),
-              state(ActivationState::kNotActive),
-              activationType(activationType) {}
-
-        const int64_t ttl_ns;
-        int64_t start_ns;
-        ActivationState state;
-        const ActivationType activationType;
-    };
     // When the metric producer has multiple activations, these activations are ORed to determine
     // whether the metric producer is ready to generate metrics.
     std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
@@ -405,12 +449,37 @@
 
     bool mIsActive;
 
+    // The slice_by_state atom ids defined in statsd_config.
+    const std::vector<int32_t> mSlicedStateAtoms;
+
+    // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
+    const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap;
+
+    // MetricStateLinks defined in statsd_config that link fields in the state
+    // atom to fields in the "what" atom.
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    SkippedBucket mCurrentSkippedBucket;
+    // Buckets that were invalidated and had their data dropped.
+    std::vector<SkippedBucket> mSkippedBuckets;
+
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
+    FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+    FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
@@ -424,6 +493,13 @@
     FRIEND_TEST(StatsLogProcessorTest,
             TestActivationOnBootMultipleActivationsDifferentActivationTypes);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
+
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+
+    FRIEND_TEST(MetricsManagerTest, TestInitialConditions);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 1fb2b1c..60de1a2 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -21,18 +21,16 @@
 #include <private/android_filesystem_config.h>
 
 #include "CountMetricProducer.h"
-#include "atoms_info.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "guardrail/StatsdStats.h"
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/SimpleLogMatchingTracker.h"
 #include "metrics_manager_util.h"
-#include "stats_util.h"
+#include "state/StateManager.h"
 #include "stats_log_util.h"
-#include "statslog.h"
-
-#include <private/android_filesystem_config.h>
+#include "stats_util.h"
+#include "statslog_statsd.h"
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_INT32;
@@ -71,6 +69,9 @@
       mTtlEndNs(-1),
       mLastReportTimeNs(currentTimeNs),
       mLastReportWallClockNs(getWallClockNs()),
+      mPullerManager(pullerManager),
+      mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+                          config.whitelisted_atom_ids().end()),
       mShouldPersistHistory(config.persist_locally()) {
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
@@ -81,12 +82,13 @@
             mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
             mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
-            mMetricIndexesWithActivation, mNoReportMetricIds);
+            mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
     mVersionStringsInReport = config.version_strings_in_metric_report();
     mInstallerInReport = config.installer_in_metric_report();
 
+    // Init allowed pushed atom uids.
     if (config.allowed_log_source_size() == 0) {
         mConfigValid = false;
         ALOGE("Log source whitelist is empty! This config won't get any data. Suggest adding at "
@@ -109,6 +111,40 @@
         }
     }
 
+    // Init default allowed pull atom uids.
+    int numPullPackages = 0;
+    for (const string& pullSource : config.default_pull_packages()) {
+        auto it = UidMap::sAidToUidMapping.find(pullSource);
+        if (it != UidMap::sAidToUidMapping.end()) {
+            numPullPackages++;
+            mDefaultPullUids.insert(it->second);
+        } else {
+            ALOGE("Default pull atom packages must be in sAidToUidMapping");
+            mConfigValid = false;
+        }
+    }
+    // Init per-atom pull atom packages.
+    for (const PullAtomPackages& pullAtomPackages : config.pull_atom_packages()) {
+        int32_t atomId = pullAtomPackages.atom_id();
+        for (const string& pullPackage : pullAtomPackages.packages()) {
+            numPullPackages++;
+            auto it = UidMap::sAidToUidMapping.find(pullPackage);
+            if (it != UidMap::sAidToUidMapping.end()) {
+                mPullAtomUids[atomId].insert(it->second);
+            } else {
+                mPullAtomPackages[atomId].insert(pullPackage);
+            }
+        }
+    }
+    if (numPullPackages > StatsdStats::kMaxPullAtomPackages) {
+        ALOGE("Too many sources in default_pull_packages and pull_atom_packages. This is likely to "
+              "be an error in the config");
+        mConfigValid = false;
+    } else {
+        initPullAtomSources();
+    }
+    mPullerManager->RegisterPullUidProvider(mConfigKey, this);
+
     // Store the sub-configs used.
     for (const auto& annotation : config.annotation()) {
         mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32());
@@ -149,6 +185,13 @@
 }
 
 MetricsManager::~MetricsManager() {
+    for (auto it : mAllMetricProducers) {
+        for (int atomId : it->getSlicedStateAtoms()) {
+            StateManager::getInstance().unregisterListener(atomId, it);
+        }
+    }
+    mPullerManager->UnregisterPullUidProvider(mConfigKey, this);
+
     VLOG("~MetricsManager()");
 }
 
@@ -168,6 +211,20 @@
     }
 }
 
+void MetricsManager::initPullAtomSources() {
+    std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+    mCombinedPullAtomUids.clear();
+    for (const auto& [atomId, uids] : mPullAtomUids) {
+        mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end());
+    }
+    for (const auto& [atomId, packages] : mPullAtomPackages) {
+        for (const string& pkg : packages) {
+            set<int32_t> uids = mUidMap->getAppUid(pkg);
+            mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end());
+        }
+    }
+}
+
 bool MetricsManager::isConfigValid() const {
     return mConfigValid;
 }
@@ -175,43 +232,81 @@
 void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
                                       const int64_t version) {
     // Inform all metric producers.
-    for (auto it : mAllMetricProducers) {
-        it->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+    for (const auto& it : mAllMetricProducers) {
+        it->notifyAppUpgrade(eventTimeNs);
     }
     // check if we care this package
-    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
-        return;
+    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
+        // We will re-initialize the whole list because we don't want to keep the multi mapping of
+        // UID<->pkg inside MetricsManager to reduce the memory usage.
+        initLogSourceWhiteList();
     }
-    // We will re-initialize the whole list because we don't want to keep the multi mapping of
-    // UID<->pkg inside MetricsManager to reduce the memory usage.
-    initLogSourceWhiteList();
+
+    for (const auto& it : mPullAtomPackages) {
+        if (it.second.find(apk) != it.second.end()) {
+            initPullAtomSources();
+            return;
+        }
+    }
 }
 
 void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
                                       const int uid) {
     // Inform all metric producers.
-    for (auto it : mAllMetricProducers) {
-        it->notifyAppRemoved(eventTimeNs, apk, uid);
+    for (const auto& it : mAllMetricProducers) {
+        it->notifyAppRemoved(eventTimeNs);
     }
     // check if we care this package
-    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
-        return;
+    if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
+        // We will re-initialize the whole list because we don't want to keep the multi mapping of
+        // UID<->pkg inside MetricsManager to reduce the memory usage.
+        initLogSourceWhiteList();
     }
-    // We will re-initialize the whole list because we don't want to keep the multi mapping of
-    // UID<->pkg inside MetricsManager to reduce the memory usage.
-    initLogSourceWhiteList();
+
+    for (const auto& it : mPullAtomPackages) {
+        if (it.second.find(apk) != it.second.end()) {
+            initPullAtomSources();
+            return;
+        }
+    }
 }
 
 void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
     // Purposefully don't inform metric producers on a new snapshot
     // because we don't need to flush partial buckets.
     // This occurs if a new user is added/removed or statsd crashes.
+    initPullAtomSources();
+
     if (mAllowedPkg.size() == 0) {
         return;
     }
     initLogSourceWhiteList();
 }
 
+void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) {
+    // Inform all metric producers.
+    for (const auto& it : mAllMetricProducers) {
+        it->onStatsdInitCompleted(eventTimeNs);
+    }
+}
+
+void MetricsManager::init() {
+    for (const auto& producer : mAllMetricProducers) {
+        producer->prepareFirstBucket();
+    }
+}
+
+vector<int32_t> MetricsManager::getPullAtomUids(int32_t atomId) {
+    std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+    vector<int32_t> uids;
+    const auto& it = mCombinedPullAtomUids.find(atomId);
+    if (it != mCombinedPullAtomUids.end()) {
+        uids.insert(uids.end(), it->second.begin(), it->second.end());
+    }
+    uids.insert(uids.end(), mDefaultPullUids.begin(), mDefaultPullUids.end());
+    return uids;
+}
+
 void MetricsManager::dumpStates(FILE* out, bool verbose) {
     fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str());
     {
@@ -265,16 +360,18 @@
         protoOutput->end(token);
     }
 
-    mLastReportTimeNs = dumpTimeStampNs;
-    mLastReportWallClockNs = getWallClockNs();
+    // Do not update the timestamps when data is not cleared to avoid timestamps from being
+    // misaligned.
+    if (erase_data) {
+        mLastReportTimeNs = dumpTimeStampNs;
+        mLastReportWallClockNs = getWallClockNs();
+    }
     VLOG("=========================Metric Reports End==========================");
 }
 
 
 bool MetricsManager::checkLogCredentials(const LogEvent& event) {
-    if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) !=
-      android::util::AtomsInfo::kWhitelistedAtoms.end())
-    {
+    if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
         return true;
     }
     std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
@@ -286,18 +383,21 @@
 }
 
 bool MetricsManager::eventSanityCheck(const LogEvent& event) {
-    if (event.GetTagId() == android::util::APP_BREADCRUMB_REPORTED) {
+    if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) {
         // Check that app breadcrumb reported fields are valid.
         status_t err = NO_ERROR;
 
         // Uid is 3rd from last field and must match the caller's uid,
         // unless that caller is statsd itself (statsd is allowed to spoof uids).
         long appHookUid = event.GetLong(event.size()-2, &err);
-        if (err != NO_ERROR ) {
+        if (err != NO_ERROR) {
             VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid");
             return false;
         }
-        int32_t loggerUid = event.GetUid();
+
+        // Because the uid within the LogEvent may have been mapped from
+        // isolated to host, map the loggerUid similarly before comparing.
+        int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid());
         if (loggerUid != appHookUid && loggerUid != AID_STATSD) {
             VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d",
                  appHookUid, loggerUid);
@@ -306,21 +406,21 @@
 
         // The state must be from 0,3. This part of code must be manually updated.
         long appHookState = event.GetLong(event.size(), &err);
-        if (err != NO_ERROR ) {
+        if (err != NO_ERROR) {
             VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field");
             return false;
         } else if (appHookState < 0 || appHookState > 3) {
             VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState);
             return false;
         }
-    } else if (event.GetTagId() == android::util::DAVEY_OCCURRED) {
+    } else if (event.GetTagId() == util::DAVEY_OCCURRED) {
         // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp.
         // Check that the davey duration is reasonable. Max length check is for privacy.
         status_t err = NO_ERROR;
 
         // Uid is the first field provided.
         long jankUid = event.GetLong(1, &err);
-        if (err != NO_ERROR ) {
+        if (err != NO_ERROR) {
             VLOG("Davey occurred had error when parsing the uid");
             return false;
         }
@@ -332,7 +432,7 @@
         }
 
         long duration = event.GetLong(event.size(), &err);
-        if (err != NO_ERROR ) {
+        if (err != NO_ERROR) {
             VLOG("Davey occurred had error when parsing the duration");
             return false;
         } else if (duration > 100000) {
@@ -555,8 +655,40 @@
     }
 }
 
+bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs,
+                                          int64_t systemElapsedTimeNs,
+                                          metadata::StatsMetadata* statsMetadata) {
+    bool metadataWritten = false;
+    metadata::ConfigKey* configKey = statsMetadata->mutable_config_key();
+    configKey->set_config_id(mConfigKey.GetId());
+    configKey->set_uid(mConfigKey.GetUid());
+    for (const auto& anomalyTracker : mAllAnomalyTrackers) {
+        metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata();
+        bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs,
+                systemElapsedTimeNs, alertMetadata);
+        if (!alertWritten) {
+            statsMetadata->mutable_alert_metadata()->RemoveLast();
+        }
+        metadataWritten |= alertWritten;
+    }
+    return metadataWritten;
+}
 
-
+void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata,
+                                  int64_t currentWallClockTimeNs,
+                                  int64_t systemElapsedTimeNs) {
+    for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
+        int64_t alertId = alertMetadata.alert_id();
+        auto it = mAlertTrackerMap.find(alertId);
+        if (it == mAlertTrackerMap.end()) {
+            ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
+            continue;
+        }
+        mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata,
+                                                           currentWallClockTimeNs,
+                                                           systemElapsedTimeNs);
+    }
+}
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 34d47d4..ad30a88 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -23,6 +23,7 @@
 #include "config/ConfigKey.h"
 #include "external/StatsPullerManager.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "logd/LogEvent.h"
 #include "matchers/LogMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -35,7 +36,7 @@
 namespace statsd {
 
 // A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager : public virtual android::RefBase {
+class MetricsManager : public virtual android::RefBase, public virtual PullUidProvider {
 public:
     MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs,
                    const int64_t currentTimeNs, const sp<UidMap>& uidMap,
@@ -69,6 +70,12 @@
 
     void onUidMapReceived(const int64_t& eventTimeNs);
 
+    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
+    void init();
+
+    vector<int32_t> getPullAtomUids(int32_t atomId) override;
+
     bool shouldWriteToDisk() const {
         return mNoReportMetricIds.size() != mAllMetricProducers.size();
     }
@@ -139,6 +146,14 @@
     void writeActiveConfigToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
 
+    // Returns true if at least one piece of metadata is written.
+    bool writeMetadataToProto(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs,
+                              metadata::StatsMetadata* statsMetadata);
+
+    void loadMetadata(const metadata::StatsMetadata& metadata,
+                      int64_t currentWallClockTimeNs,
+                      int64_t systemElapsedTimeNs);
 private:
     // For test only.
     inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -159,6 +174,8 @@
     int64_t mLastReportTimeNs;
     int64_t mLastReportWallClockNs;
 
+    sp<StatsPullerManager> mPullerManager;
+
     // The uid log sources from StatsdConfig.
     std::vector<int32_t> mAllowedUid;
 
@@ -169,13 +186,29 @@
     // Logs from uids that are not in the list will be ignored to avoid spamming.
     std::set<int32_t> mAllowedLogSources;
 
+    // To guard access to mAllowedLogSources
+    mutable std::mutex mAllowedLogSourcesMutex;
+
+    const std::set<int32_t> mWhitelistedAtomIds;
+
+    // We can pull any atom from these uids.
+    std::set<int32_t> mDefaultPullUids;
+
+    // Uids that specific atoms can pull from.
+    // This is a map<atom id, set<uids>>
+    std::map<int32_t, std::set<int32_t>> mPullAtomUids;
+
+    // Packages that specific atoms can be pulled from.
+    std::map<int32_t, std::set<std::string>> mPullAtomPackages;
+
+    // All uids to pull for this atom. NOTE: Does not include the default uids for memory.
+    std::map<int32_t, std::set<int32_t>> mCombinedPullAtomUids;
+
     // Contains the annotations passed in with StatsdConfig.
     std::list<std::pair<const int64_t, const int32_t>> mAnnotations;
 
     const bool mShouldPersistHistory;
 
-    // To guard access to mAllowedLogSources
-    mutable std::mutex mAllowedLogSourcesMutex;
 
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
@@ -230,10 +263,16 @@
     // Maps deactivation triggering event to MetricProducers.
     std::unordered_map<int, std::vector<int>> mDeactivationAtomTrackerToMetricMap;
 
+    // Maps AlertIds to the index of the corresponding AnomalyTracker stored in mAllAnomalyTrackers.
+    // The map is used in LoadMetadata to more efficiently lookup AnomalyTrackers from an AlertId.
+    std::unordered_map<int64_t, int> mAlertTrackerMap;
+
     std::vector<int> mMetricIndexesWithActivation;
 
     void initLogSourceWhiteList();
 
+    void initPullAtomSources();
+
     // The metrics that don't need to be uploaded or even reported.
     std::set<int64_t> mNoReportMetricIds;
 
@@ -253,23 +292,12 @@
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
     FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
-    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition);
-
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition);
 
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -282,6 +310,8 @@
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
+    FRIEND_TEST(MetricsManagerTest, TestLogSources);
+
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
@@ -289,12 +319,31 @@
             TestActivationOnBootMultipleActivationsDifferentActivationTypes);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
 
+    FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
+    FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
+    FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+
+    FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+    FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index fa310dc..5987a72 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -46,19 +46,22 @@
 const int FIELD_ID_TIME_BASE = 9;
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
-const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
 const int FIELD_ID_IS_ACTIVE = 14;
 // for ValueMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
+// for SkippedBuckets
 const int FIELD_ID_SKIPPED_START_MILLIS = 3;
 const int FIELD_ID_SKIPPED_END_MILLIS = 4;
+const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
+// for DumpEvent Proto
+const int FIELD_ID_BUCKET_DROP_REASON = 1;
+const int FIELD_ID_DROP_TIME = 2;
 // for ValueMetricData
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
-const int FIELD_ID_DIMENSION_IN_CONDITION = 2;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
-const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 // for ValueBucketInfo
 const int FIELD_ID_VALUE_INDEX = 1;
 const int FIELD_ID_VALUE_LONG = 2;
@@ -75,10 +78,17 @@
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 ValueMetricProducer::ValueMetricProducer(
         const ConfigKey& key, const ValueMetric& metric, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache,
         const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
         const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs,
-        const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager)
-    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard),
+        const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache,
+                     conditionWizard, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+                     stateGroupMap),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
@@ -100,11 +110,11 @@
       mSkipZeroDiffOutput(metric.skip_zero_diff_output()),
       mUseZeroDefaultBase(metric.use_zero_default_base()),
       mHasGlobalBase(false),
-      mCurrentBucketIsInvalid(false),
+      mCurrentBucketIsSkipped(false),
       mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC
                                                       : StatsdStats::kPullMaxDelayNs),
       mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()),
-      // Condition timer will be set in prepareFirstBucketLocked.
+      // Condition timer will be set later within the constructor after pulling events
       mConditionTimer(false, timeBaseNs) {
     int64_t bucketSizeMills = 0;
     if (metric.has_bucket()) {
@@ -120,10 +130,7 @@
     if (metric.has_dimensions_in_what()) {
         translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
         mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
-    }
-
-    if (metric.has_dimensions_in_condition()) {
-        translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
+        mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
     }
 
     if (metric.links().size() > 0) {
@@ -134,11 +141,16 @@
             translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
             mMetric2ConditionLinks.push_back(mc);
         }
+        mConditionSliced = true;
     }
 
-    mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) ||
-                          HasPositionALL(metric.dimensions_in_condition());
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
 
     int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs);
     mCurrentBucketNum += numBucketsForward;
@@ -146,7 +158,7 @@
     flushIfNeededLocked(startTimeNs);
 
     if (mIsPulled) {
-        mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(),
+        mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(),
                                          mBucketSizeNs);
     }
 
@@ -155,6 +167,11 @@
     // Adjust start for partial bucket
     mCurrentBucketStartTimeNs = startTimeNs;
     mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs);
+
+    // Now that activations are processed, start the condition timer if needed.
+    mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue,
+                                       mCurrentBucketStartTimeNs);
+
     VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mTimeBaseNs);
 }
@@ -162,18 +179,48 @@
 ValueMetricProducer::~ValueMetricProducer() {
     VLOG("~ValueMetricProducer() called");
     if (mIsPulled) {
-        mPullerManager->UnRegisterReceiver(mPullTagId, this);
+        mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this);
     }
 }
 
-void ValueMetricProducer::prepareFirstBucketLocked() {
-    // Kicks off the puller immediately if condition is true and diff based.
-    if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) {
-        pullAndMatchEventsLocked(mCurrentBucketStartTimeNs, mCondition);
+void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
+                                         const HashableDimensionKey& primaryKey,
+                                         const FieldValue& oldState, const FieldValue& newState) {
+    VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
+         (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
+         oldState.mValue.int_value, newState.mValue.int_value);
+
+    // If old and new states are in the same StateGroup, then we do not need to
+    // pull for this state change.
+    FieldValue oldStateCopy = oldState;
+    FieldValue newStateCopy = newState;
+    mapStateValue(atomId, &oldStateCopy);
+    mapStateValue(atomId, &newStateCopy);
+    if (oldStateCopy == newStateCopy) {
+        return;
     }
-    // Now that activations are processed, start the condition timer if needed.
-    mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue,
-                                       mCurrentBucketStartTimeNs);
+
+    // If condition is not true or metric is not active, we do not need to pull
+    // for this state change.
+    if (mCondition != ConditionState::kTrue || !mIsActive) {
+        return;
+    }
+
+    bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs;
+    if (isEventLate) {
+        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+             (long long)mCurrentBucketStartTimeNs);
+        invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
+        return;
+    }
+    mStateChangePrimaryKey.first = atomId;
+    mStateChangePrimaryKey.second = primaryKey;
+    if (mIsPulled) {
+        pullAndMatchEventsLocked(eventTimeNs);
+    }
+    mStateChangePrimaryKey.first = 0;
+    mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY;
+    flushIfNeededLocked(eventTimeNs);
 }
 
 void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
@@ -183,11 +230,9 @@
 
 void ValueMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
     StatsdStats::getInstance().noteBucketDropped(mMetricId);
-    // We are going to flush the data without doing a pull first so we need to invalidte the data.
-    bool pullNeeded = mIsPulled && mCondition == ConditionState::kTrue;
-    if (pullNeeded) {
-        invalidateCurrentBucket();
-    }
+
+    // The current partial bucket is not flushed and does not require a pull,
+    // so the data is still valid.
     flushIfNeededLocked(dropTimeNs);
     clearPastBucketsLocked(dropTimeNs);
 }
@@ -213,10 +258,10 @@
         if (pullNeeded) {
             switch (dumpLatency) {
                 case FAST:
-                    invalidateCurrentBucket();
+                    invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED);
                     break;
                 case NO_TIME_CONSTRAINTS:
-                    pullAndMatchEventsLocked(dumpTimeNs, mCondition);
+                    pullAndMatchEventsLocked(dumpTimeNs);
                     break;
             }
         }
@@ -238,23 +283,26 @@
             writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
             protoOutput->end(dimenPathToken);
         }
-        if (!mDimensionsInCondition.empty()) {
-            uint64_t dimenPathToken =
-                    protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION);
-            writeDimensionPathToProto(mDimensionsInCondition, protoOutput);
-            protoOutput->end(dimenPathToken);
-        }
     }
 
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
 
-    for (const auto& pair : mSkippedBuckets) {
+    for (const auto& skippedBucket : mSkippedBuckets) {
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
         protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
-                           (long long)(NanoToMillis(pair.first)));
+                           (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
         protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
-                           (long long)(NanoToMillis(pair.second)));
+                           (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
+        for (const auto& dropEvent : skippedBucket.dropEvents) {
+            uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                         FIELD_ID_SKIPPED_DROP_EVENT);
+            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
+            protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME,
+                               (long long)(NanoToMillis(dropEvent.dropTimeNs)));
+            ;
+            protoOutput->end(dropEventToken);
+        }
         protoOutput->end(wrapperToken);
     }
 
@@ -270,21 +318,17 @@
                     protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
             protoOutput->end(dimensionToken);
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                uint64_t dimensionInConditionToken =
-                        protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
-                writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), str_set,
-                                      protoOutput);
-                protoOutput->end(dimensionInConditionToken);
-            }
         } else {
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
-            if (dimensionKey.hasDimensionKeyInCondition()) {
-                writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(),
-                                               FIELD_ID_DIMENSION_LEAF_IN_CONDITION, str_set,
-                                               protoOutput);
-            }
+        }
+
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
         }
 
         // Then fill bucket_info (ValueBucketInfo).
@@ -306,7 +350,7 @@
                 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS,
                                    (long long)bucket.mConditionTrueNs);
             }
-            for (int i = 0; i < (int)bucket.valueIndex.size(); i ++) {
+            for (int i = 0; i < (int)bucket.valueIndex.size(); i++) {
                 int index = bucket.valueIndex[i];
                 const Value& value = bucket.values[i];
                 uint64_t valueToken = protoOutput->start(
@@ -341,23 +385,34 @@
     }
 }
 
-void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase() {
-    if (!mCurrentBucketIsInvalid) {
-        // Only report once per invalid bucket.
+void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
+                                                                  const BucketDropReason reason) {
+    if (!mCurrentBucketIsSkipped) {
+        // Only report to StatsdStats once per invalid bucket.
         StatsdStats::getInstance().noteInvalidatedBucket(mMetricId);
     }
-    mCurrentBucketIsInvalid = true;
+
+    skipCurrentBucket(dropTimeNs, reason);
 }
 
-void ValueMetricProducer::invalidateCurrentBucket() {
-    invalidateCurrentBucketWithoutResetBase();
+void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs,
+                                                  const BucketDropReason reason) {
+    invalidateCurrentBucketWithoutResetBase(dropTimeNs, reason);
     resetBase();
 }
 
+void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs,
+                                            const BucketDropReason reason) {
+    if (!maxDropEventsReached()) {
+        mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason));
+    }
+    mCurrentBucketIsSkipped = true;
+}
+
 void ValueMetricProducer::resetBase() {
-    for (auto& slice : mCurrentSlicedBucket) {
-        for (auto& interval : slice.second) {
-            interval.hasBase = false;
+    for (auto& slice : mCurrentBaseInfo) {
+        for (auto& baseInfo : slice.second) {
+            baseInfo.hasBase = false;
         }
     }
     mHasGlobalBase = false;
@@ -369,9 +424,10 @@
 // - ConditionTimer tracks changes based on AND of condition and active state.
 void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) {
     bool isEventTooLate  = eventTimeNs < mCurrentBucketStartTimeNs;
-    if (ConditionState::kTrue == mCondition && isEventTooLate) {
+    if (isEventTooLate) {
         // Drop bucket because event arrived too late, ie. we are missing data for this bucket.
-        invalidateCurrentBucket();
+        StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
+        invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
     }
 
     // Call parent method once we've verified the validity of current bucket.
@@ -384,7 +440,7 @@
     // Pull on active state changes.
     if (!isEventTooLate) {
         if (mIsPulled) {
-            pullAndMatchEventsLocked(eventTimeNs, mCondition);
+            pullAndMatchEventsLocked(eventTimeNs);
         }
         // When active state changes from true to false, clear diff base but don't
         // reset other counters as we may accumulate more value in the bucket.
@@ -404,65 +460,80 @@
     ConditionState newCondition = condition ? ConditionState::kTrue : ConditionState::kFalse;
     bool isEventTooLate  = eventTimeNs < mCurrentBucketStartTimeNs;
 
-    if (mIsActive) {
-        if (isEventTooLate) {
-            VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
-                 (long long)mCurrentBucketStartTimeNs);
-            StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId);
-            invalidateCurrentBucket();
-        } else {
-            if (mCondition == ConditionState::kUnknown) {
-                // If the condition was unknown, we mark the bucket as invalid since the bucket will
-                // contain partial data. For instance, the condition change might happen close to
-                // the end of the bucket and we might miss lots of data.
-                //
-                // We still want to pull to set the base.
-                invalidateCurrentBucket();
-            }
-
-            // Pull on condition changes.
-            bool conditionChanged =
-                    (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse)
-                    || (mCondition == ConditionState::kFalse &&
-                            newCondition == ConditionState::kTrue);
-            // We do not need to pull when we go from unknown to false.
-            //
-            // We also pull if the condition was already true in order to be able to flush the
-            // bucket at the end if needed.
-            //
-            // onConditionChangedLocked might happen on bucket boundaries if this is called before
-            // #onDataPulled.
-            if (mIsPulled && (conditionChanged || condition)) {
-                pullAndMatchEventsLocked(eventTimeNs, newCondition);
-            }
-
-            // When condition change from true to false, clear diff base but don't
-            // reset other counters as we may accumulate more value in the bucket.
-            if (mUseDiff && mCondition == ConditionState::kTrue
-                    && newCondition == ConditionState::kFalse) {
-                resetBase();
-            }
-        }
-    }
-
-    mCondition = isEventTooLate ? initialCondition(mConditionTrackerIndex) : newCondition;
-
-    if (mIsActive) {
-        flushIfNeededLocked(eventTimeNs);
-        mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
-    }
-}
-
-void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs,
-        ConditionState condition) {
-    vector<std::shared_ptr<LogEvent>> allData;
-    if (!mPullerManager->Pull(mPullTagId, &allData)) {
-        ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs);
-        invalidateCurrentBucket();
+    // If the config is not active, skip the event.
+    if (!mIsActive) {
+        mCondition = isEventTooLate ? ConditionState::kUnknown : newCondition;
         return;
     }
 
-    accumulateEvents(allData, timestampNs, timestampNs, condition);
+    // If the event arrived late, mark the bucket as invalid and skip the event.
+    if (isEventTooLate) {
+        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+             (long long)mCurrentBucketStartTimeNs);
+        StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
+        StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId);
+        invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
+        mCondition = ConditionState::kUnknown;
+        mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
+        return;
+    }
+
+    // If the previous condition was unknown, mark the bucket as invalid
+    // because the bucket will contain partial data. For example, the condition
+    // change might happen close to the end of the bucket and we might miss a
+    // lot of data.
+    //
+    // We still want to pull to set the base.
+    if (mCondition == ConditionState::kUnknown) {
+        invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
+    }
+
+    // Pull and match for the following condition change cases:
+    // unknown/false -> true - condition changed
+    // true -> false - condition changed
+    // true -> true - old condition was true so we can flush the bucket at the
+    // end if needed.
+    //
+    // We don’t need to pull for unknown -> false or false -> false.
+    //
+    // onConditionChangedLocked might happen on bucket boundaries if this is
+    // called before #onDataPulled.
+    if (mIsPulled &&
+        (newCondition == ConditionState::kTrue || mCondition == ConditionState::kTrue)) {
+        pullAndMatchEventsLocked(eventTimeNs);
+    }
+
+    // For metrics that use diff, when condition changes from true to false,
+    // clear diff base but don't reset other counts because we may accumulate
+    // more value in the bucket.
+    if (mUseDiff &&
+        (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse)) {
+        resetBase();
+    }
+
+    // Update condition state after pulling.
+    mCondition = newCondition;
+
+    flushIfNeededLocked(eventTimeNs);
+    mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
+}
+
+void ValueMetricProducer::prepareFirstBucketLocked() {
+    // Kicks off the puller immediately if condition is true and diff based.
+    if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) {
+        pullAndMatchEventsLocked(mCurrentBucketStartTimeNs);
+    }
+}
+
+void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) {
+    vector<std::shared_ptr<LogEvent>> allData;
+    if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) {
+        ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs);
+        invalidateCurrentBucket(timestampNs, BucketDropReason::PULL_FAILED);
+        return;
+    }
+
+    accumulateEvents(allData, timestampNs, timestampNs);
 }
 
 int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTimeNs) {
@@ -475,33 +546,33 @@
 void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData,
                                        bool pullSuccess, int64_t originalPullTimeNs) {
     std::lock_guard<std::mutex> lock(mMutex);
-        if (mCondition == ConditionState::kTrue) {
-            // If the pull failed, we won't be able to compute a diff.
-            if (!pullSuccess) {
-                invalidateCurrentBucket();
+    if (mCondition == ConditionState::kTrue) {
+        // If the pull failed, we won't be able to compute a diff.
+        if (!pullSuccess) {
+            invalidateCurrentBucket(originalPullTimeNs, BucketDropReason::PULL_FAILED);
+        } else {
+            bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs();
+            if (isEventLate) {
+                // If the event is late, we are in the middle of a bucket. Just
+                // process the data without trying to snap the data to the nearest bucket.
+                accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs);
             } else {
-                bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs();
-                if (isEventLate) {
-                    // If the event is late, we are in the middle of a bucket. Just
-                    // process the data without trying to snap the data to the nearest bucket.
-                    accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs, mCondition);
-                } else {
-                    // For scheduled pulled data, the effective event time is snap to the nearest
-                    // bucket end. In the case of waking up from a deep sleep state, we will
-                    // attribute to the previous bucket end. If the sleep was long but not very
-                    // long, we will be in the immediate next bucket. Previous bucket may get a
-                    // larger number as we pull at a later time than real bucket end.
-                    //
-                    // If the sleep was very long, we skip more than one bucket before sleep. In
-                    // this case, if the diff base will be cleared and this new data will serve as
-                    // new diff base.
-                    int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1;
-                    StatsdStats::getInstance().noteBucketBoundaryDelayNs(
-                            mMetricId, originalPullTimeNs - bucketEndTime);
-                    accumulateEvents(allData, originalPullTimeNs, bucketEndTime, mCondition);
-                }
+                // For scheduled pulled data, the effective event time is snap to the nearest
+                // bucket end. In the case of waking up from a deep sleep state, we will
+                // attribute to the previous bucket end. If the sleep was long but not very
+                // long, we will be in the immediate next bucket. Previous bucket may get a
+                // larger number as we pull at a later time than real bucket end.
+                //
+                // If the sleep was very long, we skip more than one bucket before sleep. In
+                // this case, if the diff base will be cleared and this new data will serve as
+                // new diff base.
+                int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1;
+                StatsdStats::getInstance().noteBucketBoundaryDelayNs(
+                        mMetricId, originalPullTimeNs - bucketEndTime);
+                accumulateEvents(allData, originalPullTimeNs, bucketEndTime);
             }
         }
+    }
 
     // We can probably flush the bucket. Since we used bucketEndTime when calling
     // #onMatchedLogEventInternalLocked, the current bucket will not have been flushed.
@@ -509,18 +580,18 @@
 }
 
 void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData,
-                                           int64_t originalPullTimeNs, int64_t eventElapsedTimeNs,
-                                           ConditionState condition) {
+                                           int64_t originalPullTimeNs, int64_t eventElapsedTimeNs) {
     bool isEventLate = eventElapsedTimeNs < mCurrentBucketStartTimeNs;
     if (isEventLate) {
         VLOG("Skip bucket end pull due to late arrival: %lld vs %lld",
              (long long)eventElapsedTimeNs, (long long)mCurrentBucketStartTimeNs);
         StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
-        invalidateCurrentBucket();
+        invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
         return;
     }
 
-    const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs;
+    const int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
+    const int64_t pullDelayNs = elapsedRealtimeNs - originalPullTimeNs;
     StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
     if (pullDelayNs > mMaxPullDelayNs) {
         ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId,
@@ -528,15 +599,10 @@
         StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
         // We are missing one pull from the bucket which means we will not have a complete view of
         // what's going on.
-        invalidateCurrentBucket();
+        invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::PULL_DELAYED);
         return;
     }
 
-    if (allData.size() == 0) {
-        VLOG("Data pulled is empty");
-        StatsdStats::getInstance().noteEmptyData(mPullTagId);
-    }
-
     mMatchedMetricDimensionKeys.clear();
     for (const auto& data : allData) {
         LogEvent localCopy = data->makeCopy();
@@ -546,14 +612,19 @@
             onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
         }
     }
-    // If the new pulled data does not contains some keys we track in our intervals, we need to
-    // reset the base.
+    // If a key that is:
+    // 1. Tracked in mCurrentSlicedBucket and
+    // 2. A superset of the current mStateChangePrimaryKey
+    // was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys)
+    // then we need to reset the base.
     for (auto& slice : mCurrentSlicedBucket) {
-        bool presentInPulledData = mMatchedMetricDimensionKeys.find(slice.first)
-                != mMatchedMetricDimensionKeys.end();
-        if (!presentInPulledData) {
-            for (auto& interval : slice.second) {
-                interval.hasBase = false;
+        const auto& whatKey = slice.first.getDimensionKeyInWhat();
+        bool presentInPulledData =
+                mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end();
+        if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) {
+            auto it = mCurrentBaseInfo.find(whatKey);
+            for (auto& baseInfo : it->second) {
+                baseInfo.hasBase = false;
             }
         }
     }
@@ -567,7 +638,7 @@
     // incorrectly compute the diff when mUseZeroDefaultBase is true since an existing key
     // might be missing from mCurrentSlicedBucket.
     if (hasReachedGuardRailLimit()) {
-        invalidateCurrentBucket();
+        invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::DIMENSION_GUARDRAIL_REACHED);
         mCurrentSlicedBucket.clear();
     }
 }
@@ -582,10 +653,10 @@
     if (verbose) {
         for (const auto& it : mCurrentSlicedBucket) {
           for (const auto& interval : it.second) {
-            fprintf(out, "\t(what)%s\t(condition)%s  (value)%s\n",
-                    it.first.getDimensionKeyInWhat().toString().c_str(),
-                    it.first.getDimensionKeyInCondition().toString().c_str(),
-                    interval.value.toString().c_str());
+              fprintf(out, "\t(what)%s\t(states)%s  (value)%s\n",
+                      it.first.getDimensionKeyInWhat().toString().c_str(),
+                      it.first.getStateValuesKey().toString().c_str(),
+                      interval.value.toString().c_str());
           }
         }
     }
@@ -653,6 +724,7 @@
                     ret.setDouble(value.mValue.double_value);
                     break;
                 default:
+                    return false;
                     break;
             }
             return true;
@@ -661,17 +733,30 @@
     return false;
 }
 
-void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIndex,
-                                                          const MetricDimensionKey& eventKey,
-                                                          const ConditionKey& conditionKey,
-                                                          bool condition, const LogEvent& event) {
+void ValueMetricProducer::onMatchedLogEventInternalLocked(
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
+        const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+        const map<int, HashableDimensionKey>& statePrimaryKeys) {
+    auto whatKey = eventKey.getDimensionKeyInWhat();
+    auto stateKey = eventKey.getStateValuesKey();
+
+    // Skip this event if a state changed occurred for a different primary key.
+    auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first);
+    // Check that both the atom id and the primary key are equal.
+    if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) {
+        VLOG("ValueMetric skip event with primary key %s because state change primary key "
+             "is %s",
+             it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str());
+        return;
+    }
+
     int64_t eventTimeNs = event.GetElapsedTimestampNs();
     if (eventTimeNs < mCurrentBucketStartTimeNs) {
         VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
              (long long)mCurrentBucketStartTimeNs);
         return;
     }
-    mMatchedMetricDimensionKeys.insert(eventKey);
+    mMatchedMetricDimensionKeys.insert(whatKey);
 
     if (!mIsPulled) {
         // We cannot flush without doing a pull first.
@@ -689,17 +774,35 @@
     bool shouldSkipForPulledMetric = mIsPulled && !mUseDiff
             && mCondition != ConditionState::kTrue;
     if (shouldSkipForPushMetric || shouldSkipForPulledMetric) {
-        VLOG("ValueMetric skip event because condition is false");
+        VLOG("ValueMetric skip event because condition is false and we are not using diff (for "
+             "pulled metric)");
         return;
     }
 
     if (hitGuardRailLocked(eventKey)) {
         return;
     }
-    vector<Interval>& multiIntervals = mCurrentSlicedBucket[eventKey];
-    if (multiIntervals.size() < mFieldMatchers.size()) {
+
+    vector<BaseInfo>& baseInfos = mCurrentBaseInfo[whatKey];
+    if (baseInfos.size() < mFieldMatchers.size()) {
         VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
-        multiIntervals.resize(mFieldMatchers.size());
+        baseInfos.resize(mFieldMatchers.size());
+    }
+
+    for (BaseInfo& baseInfo : baseInfos) {
+        if (!baseInfo.hasCurrentState) {
+            baseInfo.currentState = getUnknownStateKey();
+            baseInfo.hasCurrentState = true;
+        }
+    }
+
+    // We need to get the intervals stored with the previous state key so we can
+    // close these value intervals.
+    const auto oldStateKey = baseInfos[0].currentState;
+    vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)];
+    if (intervals.size() < mFieldMatchers.size()) {
+        VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
+        intervals.resize(mFieldMatchers.size());
     }
 
     // We only use anomaly detection under certain cases.
@@ -712,9 +815,12 @@
 
     for (int i = 0; i < (int)mFieldMatchers.size(); i++) {
         const Matcher& matcher = mFieldMatchers[i];
-        Interval& interval = multiIntervals[i];
+        BaseInfo& baseInfo = baseInfos[i];
+        Interval& interval = intervals[i];
         interval.valueIndex = i;
         Value value;
+        baseInfo.hasCurrentState = true;
+        baseInfo.currentState = stateKey;
         if (!getDoubleOrLong(event, matcher, value)) {
             VLOG("Failed to get value %d from event %s", i, event.ToString().c_str());
             StatsdStats::getInstance().noteBadValueType(mMetricId);
@@ -723,60 +829,61 @@
         interval.seenNewData = true;
 
         if (mUseDiff) {
-            if (!interval.hasBase) {
+            if (!baseInfo.hasBase) {
                 if (mHasGlobalBase && mUseZeroDefaultBase) {
                     // The bucket has global base. This key does not.
                     // Optionally use zero as base.
-                    interval.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE);
-                    interval.hasBase = true;
+                    baseInfo.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE);
+                    baseInfo.hasBase = true;
                 } else {
                     // no base. just update base and return.
-                    interval.base = value;
-                    interval.hasBase = true;
+                    baseInfo.base = value;
+                    baseInfo.hasBase = true;
                     // If we're missing a base, do not use anomaly detection on incomplete data
                     useAnomalyDetection = false;
-                    // Continue (instead of return) here in order to set interval.base and
-                    // interval.hasBase for other intervals
+                    // Continue (instead of return) here in order to set baseInfo.base and
+                    // baseInfo.hasBase for other baseInfos
                     continue;
                 }
             }
+
             Value diff;
             switch (mValueDirection) {
                 case ValueMetric::INCREASING:
-                    if (value >= interval.base) {
-                        diff = value - interval.base;
+                    if (value >= baseInfo.base) {
+                        diff = value - baseInfo.base;
                     } else if (mUseAbsoluteValueOnReset) {
                         diff = value;
                     } else {
                         VLOG("Unexpected decreasing value");
                         StatsdStats::getInstance().notePullDataError(mPullTagId);
-                        interval.base = value;
+                        baseInfo.base = value;
                         // If we've got bad data, do not use anomaly detection
                         useAnomalyDetection = false;
                         continue;
                     }
                     break;
                 case ValueMetric::DECREASING:
-                    if (interval.base >= value) {
-                        diff = interval.base - value;
+                    if (baseInfo.base >= value) {
+                        diff = baseInfo.base - value;
                     } else if (mUseAbsoluteValueOnReset) {
                         diff = value;
                     } else {
                         VLOG("Unexpected increasing value");
                         StatsdStats::getInstance().notePullDataError(mPullTagId);
-                        interval.base = value;
+                        baseInfo.base = value;
                         // If we've got bad data, do not use anomaly detection
                         useAnomalyDetection = false;
                         continue;
                     }
                     break;
                 case ValueMetric::ANY:
-                    diff = value - interval.base;
+                    diff = value - baseInfo.base;
                     break;
                 default:
                     break;
             }
-            interval.base = value;
+            baseInfo.base = value;
             value = diff;
         }
 
@@ -806,7 +913,7 @@
     // Only trigger the tracker if all intervals are correct
     if (useAnomalyDetection) {
         // TODO: propgate proper values down stream when anomaly support doubles
-        long wholeBucketVal = multiIntervals[0].value.long_value;
+        long wholeBucketVal = intervals[0].value.long_value;
         auto prev = mCurrentFullBucket.find(eventKey);
         if (prev != mCurrentFullBucket.end()) {
             wholeBucketVal += prev->second;
@@ -823,7 +930,7 @@
 void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
     int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
     if (eventTimeNs < currentBucketEndTimeNs) {
-        VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+        VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs,
              (long long)(currentBucketEndTimeNs));
         return;
     }
@@ -844,25 +951,39 @@
                                                    const int64_t& nextBucketStartTimeNs) {
     if (mCondition == ConditionState::kUnknown) {
         StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId);
-    }
-
-    int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs);
-    if (numBucketsForward > 1) {
-        VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
-        StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId);
-        // Something went wrong. Maybe the device was sleeping for a long time. It is better
-        // to mark the current bucket as invalid. The last pull might have been successful through.
-        invalidateCurrentBucketWithoutResetBase();
+        invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
     }
 
     VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
          (int)mCurrentSlicedBucket.size());
+
     int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
-    int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs;
+    int64_t bucketEndTime = fullBucketEndTimeNs;
+    int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs);
+
+    // Skip buckets if this is a pulled metric or a pushed metric that is diffed.
+    if (numBucketsForward > 1 && (mIsPulled || mUseDiff)) {
+
+        VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
+        StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId);
+        // Something went wrong. Maybe the device was sleeping for a long time. It is better
+        // to mark the current bucket as invalid. The last pull might have been successful through.
+        invalidateCurrentBucketWithoutResetBase(eventTimeNs,
+                                                BucketDropReason::MULTIPLE_BUCKETS_SKIPPED);
+        // End the bucket at the next bucket start time so the entire interval is skipped.
+        bucketEndTime = nextBucketStartTimeNs;
+    } else if (eventTimeNs < fullBucketEndTimeNs) {
+        bucketEndTime = eventTimeNs;
+    }
+
     // Close the current bucket.
     int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime);
     bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
-    if (isBucketLargeEnough && !mCurrentBucketIsInvalid) {
+    if (!isBucketLargeEnough) {
+        skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL);
+    }
+    if (!mCurrentBucketIsSkipped) {
+        bool bucketHasData = false;
         // The current bucket is large enough to keep.
         for (const auto& slice : mCurrentSlicedBucket) {
             ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second);
@@ -871,13 +992,34 @@
             if (bucket.valueIndex.size() > 0) {
                 auto& bucketList = mPastBuckets[slice.first];
                 bucketList.push_back(bucket);
+                bucketHasData = true;
             }
         }
-    } else {
-        mSkippedBuckets.emplace_back(mCurrentBucketStartTimeNs, bucketEndTime);
+        if (!bucketHasData) {
+            skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA);
+        }
     }
 
-    appendToFullBucket(eventTimeNs, fullBucketEndTimeNs);
+    if (mCurrentBucketIsSkipped) {
+        mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
+        mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
+        mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
+    }
+
+    // This means that the current bucket was not flushed before a forced bucket split.
+    // This can happen if an app update or a dump report with include_current_partial_bucket is
+    // requested before we get a chance to flush the bucket due to receiving new data, either from
+    // the statsd socket or the StatsPullerManager.
+    if (bucketEndTime < nextBucketStartTimeNs) {
+        SkippedBucket bucketInGap;
+        bucketInGap.bucketStartTimeNs = bucketEndTime;
+        bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs;
+        bucketInGap.dropEvents.emplace_back(
+                buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA));
+        mSkippedBuckets.emplace_back(bucketInGap);
+    }
+
+    appendToFullBucket(eventTimeNs > fullBucketEndTimeNs);
     initCurrentSlicedBucket(nextBucketStartTimeNs);
     // Update the condition timer again, in case we skipped buckets.
     mConditionTimer.newBucketStart(nextBucketStartTimeNs);
@@ -927,22 +1069,24 @@
         } else {
             it++;
         }
+        // TODO(b/157655103): remove mCurrentBaseInfo entries when obsolete
     }
 
-    mCurrentBucketIsInvalid = false;
+    mCurrentBucketIsSkipped = false;
+    mCurrentSkippedBucket.reset();
+
     // If we do not have a global base when the condition is true,
     // we will have incomplete bucket for the next bucket.
     if (mUseDiff && !mHasGlobalBase && mCondition) {
-        mCurrentBucketIsInvalid = false;
+        mCurrentBucketIsSkipped = false;
     }
     mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
     VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
 }
 
-void ValueMetricProducer::appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs) {
-    bool isFullBucketReached = eventTimeNs > fullBucketEndTimeNs;
-    if (mCurrentBucketIsInvalid) {
+void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) {
+    if (mCurrentBucketIsSkipped) {
         if (isFullBucketReached) {
             // If the bucket is invalid, we ignore the full bucket since it contains invalid data.
             mCurrentFullBucket.clear();
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 784ac64..b359af7 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -50,12 +50,18 @@
 // - an alarm set to the end of the bucket
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
-                        const int whatMatcherIndex,
-                        const sp<EventMatcherWizard>& matcherWizard,
-                        const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs,
-                        const sp<StatsPullerManager>& pullerManager);
+    ValueMetricProducer(
+            const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache,
+            const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+            const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+            const int64_t timeBaseNs, const int64_t startTimeNs,
+            const sp<StatsPullerManager>& pullerManager,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~ValueMetricProducer();
 
@@ -64,23 +70,34 @@
                       bool pullSuccess, int64_t originalPullTimeNs) override;
 
     // ValueMetric needs special logic if it's a pulled atom.
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override {
+    void notifyAppUpgrade(const int64_t& eventTimeNs) override {
         std::lock_guard<std::mutex> lock(mMutex);
         if (!mSplitBucketForAppUpgrade) {
             return;
         }
-        if (mIsPulled && mCondition) {
-            pullAndMatchEventsLocked(eventTimeNs, mCondition);
+        if (mIsPulled && mCondition == ConditionState::kTrue) {
+            pullAndMatchEventsLocked(eventTimeNs);
         }
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
     };
 
+    // ValueMetric needs special logic if it's a pulled atom.
+    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mIsPulled && mCondition == ConditionState::kTrue) {
+            pullAndMatchEventsLocked(eventTimeNs);
+        }
+        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
+
+    void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
+                        const FieldValue& oldState, const FieldValue& newState) override;
+
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
-            const ConditionKey& conditionKey, bool condition,
-            const LogEvent& event) override;
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
 
 private:
     void onDumpReportLocked(const int64_t dumpTimeNs,
@@ -125,8 +142,15 @@
     int64_t calcBucketsForwardCount(const int64_t& eventTimeNs) const;
 
     // Mark the data as invalid.
-    void invalidateCurrentBucket();
-    void invalidateCurrentBucketWithoutResetBase();
+    void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
+
+    void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
+                                                 const BucketDropReason reason);
+
+    // Skips the current bucket without notifying StatsdStats of the skipped bucket.
+    // This should only be called from #flushCurrentBucketLocked. Otherwise, a future event that
+    // causes the bucket to be invalidated will not notify StatsdStats.
+    void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
 
     const int mWhatMatcherIndex;
 
@@ -138,7 +162,10 @@
     std::vector<Matcher> mFieldMatchers;
 
     // Value fields for matching.
-    std::set<MetricDimensionKey> mMatchedMetricDimensionKeys;
+    std::set<HashableDimensionKey> mMatchedMetricDimensionKeys;
+
+    // Holds the atom id, primary key pair from a state change.
+    pair<int32_t, HashableDimensionKey> mStateChangePrimaryKey;
 
     // tagId for pulled data. -1 if this is not pulled
     const int mPullTagId;
@@ -150,10 +177,6 @@
     typedef struct {
         // Index in multi value aggregation.
         int valueIndex;
-        // Holds current base value of the dimension. Take diff and update if necessary.
-        Value base;
-        // Whether there is a base to diff to.
-        bool hasBase;
         // Current value, depending on the aggregation type.
         Value value;
         // Number of samples collected.
@@ -165,34 +188,46 @@
         bool seenNewData = false;
     } Interval;
 
+    typedef struct {
+        // Holds current base value of the dimension. Take diff and update if necessary.
+        Value base;
+        // Whether there is a base to diff to.
+        bool hasBase;
+        // Last seen state value(s).
+        HashableDimensionKey currentState;
+        // Whether this dimensions in what key has a current state key.
+        bool hasCurrentState;
+    } BaseInfo;
+
     std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket;
 
+    std::unordered_map<HashableDimensionKey, std::vector<BaseInfo>> mCurrentBaseInfo;
+
     std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
 
-    // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped.
-    std::list<std::pair<int64_t, int64_t>> mSkippedBuckets;
-
     const int64_t mMinBucketSizeNs;
 
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const MetricDimensionKey& newKey);
+
     bool hasReachedGuardRailLimit() const;
 
     bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey);
 
-    void pullAndMatchEventsLocked(const int64_t timestampNs, ConditionState condition);
+    void pullAndMatchEventsLocked(const int64_t timestampNs);
 
     void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData,
-                          int64_t originalPullTimeNs, int64_t eventElapsedTimeNs,
-                          ConditionState condition);
+                          int64_t originalPullTimeNs, int64_t eventElapsedTimeNs);
 
     ValueBucket buildPartialBucket(int64_t bucketEndTime,
                                    const std::vector<Interval>& intervals);
+
     void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs);
-    void appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs);
+
+    void appendToFullBucket(const bool isFullBucketReached);
 
     // Reset diff base and mHasGlobalBase
     void resetBase();
@@ -225,11 +260,9 @@
     // diff against.
     bool mHasGlobalBase;
 
-    // Invalid bucket. There was a problem in collecting data in the current bucket so we cannot
-    // trust any of the data in this bucket.
-    //
-    // For instance, one pull failed.
-    bool mCurrentBucketIsInvalid;
+    // This is to track whether or not the bucket is skipped for any of the reasons listed in
+    // BucketDropReason, many of which make the bucket potentially invalid.
+    bool mCurrentBucketIsSkipped;
 
     const int64_t mMaxPullDelayNs;
 
@@ -239,12 +272,10 @@
 
     FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
     FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange);
-    FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2);
-    FRIEND_TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet);
     FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime);
     FRIEND_TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged);
@@ -253,14 +284,8 @@
     FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled);
     FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket);
-    FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid);
-    FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit);
-    FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed);
-    FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed);
-    FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed);
     FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff);
     FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff);
-    FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated);
     FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue);
@@ -271,15 +296,12 @@
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering);
-    FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade);
-    FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition);
-    FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded);
     FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange);
@@ -288,9 +310,28 @@
     FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate);
     FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput);
     FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue);
+    FRIEND_TEST(ValueMetricProducerTest, TestSlicedState);
+    FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap);
+    FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions);
+    FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);
+
+    FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed);
+    FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed);
+    FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed);
+    FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit);
+    FRIEND_TEST(ValueMetricProducerTest_BucketDrop,
+                TestInvalidBucketWhenAccumulateEventWrongBucket);
+
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse);
+
     friend class ValueMetricProducerTestHelper;
 };
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 081e61e..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
     int64_t mDuration;
 };
 
+struct DurationValues {
+    // Recorded duration for current partial bucket.
+    int64_t mDuration;
+
+    // Sum of past partial bucket durations in current full bucket.
+    // Used for anomaly detection.
+    int64_t mDurationFullBucket;
+};
+
 class DurationTracker {
 public:
     DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex,
-                    const std::vector<Matcher>& dimensionInCondition, bool nesting,
+                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -70,11 +78,9 @@
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
           mBucketSizeNs(bucketSizeNs),
-          mDimensionInCondition(dimensionInCondition),
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
-          mDurationFullBucket(0),
           mCurrentBucketNum(currentBucketNum),
           mStartTimeNs(startTimeNs),
           mConditionSliced(conditionSliced),
@@ -83,10 +89,8 @@
 
     virtual ~DurationTracker(){};
 
-    virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0;
-
-    virtual void noteStart(const HashableDimensionKey& key, bool condition,
-                           const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+                           const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                           const bool stopAll) = 0;
     virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -94,6 +98,9 @@
     virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
     virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
 
+    virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                const FieldValue& newState) = 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(
@@ -112,9 +119,12 @@
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
 
-    void setEventKey(const MetricDimensionKey& eventKey) {
-         mEventKey = eventKey;
-    }
+    virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+    virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+    // Replace old value with new value for the given state atom.
+    virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
 
 protected:
     int64_t getCurrentBucketEndTimeNs() const {
@@ -143,10 +153,11 @@
         }
     }
 
-    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+    void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+                                        const int64_t& bucketValue, const int64_t& bucketNum) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
-                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+                anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
             }
         }
     }
@@ -167,6 +178,10 @@
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
 
+    void setEventKey(const MetricDimensionKey& eventKey) {
+        mEventKey = eventKey;
+    }
+
     // A reference to the DurationMetricProducer's config key.
     const ConfigKey& mConfigKey;
 
@@ -180,15 +195,14 @@
 
     const int64_t mBucketSizeNs;
 
-    const std::vector<Matcher>& mDimensionInCondition;
-
     const bool mNested;
 
     int64_t mCurrentBucketStartTimeNs;
 
     int64_t mDuration;  // current recorded duration result (for partial bucket)
 
-    int64_t mDurationFullBucket;  // Sum of past partial buckets in current full bucket.
+    // Recorded duration results for each state key in the current partial bucket.
+    std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
 
     int64_t mCurrentBucketNum;
 
@@ -196,7 +210,6 @@
 
     const bool mConditionSliced;
 
-    bool mSameConditionDimensionsInTracker;
     bool mHasLinksToAllConditionDimensionsInTracker;
 
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 6868b8c..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,37 +26,14 @@
 
 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                                        const MetricDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex,
-                                       const vector<Matcher>& dimensionInCondition, bool nesting,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                                        int64_t startTimeNs, int64_t bucketSizeNs,
                                        bool conditionSliced, bool fullLink,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers) {
-    if (mWizard != nullptr) {
-        mSameConditionDimensionsInTracker =
-            mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
-    }
-}
-
-unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<MaxDurationTracker>(*this);
-    for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) {
-        if (it->second.state  != kStopped) {
-            it->second.lastStartTime = eventTime;
-            it->second.lastDuration = 0;
-            it++;
-        } else {
-            it = clonedTracker->mInfos.erase(it);
-        }
-    }
-    if (clonedTracker->mInfos.empty()) {
-        return nullptr;
-    } else {
-        return clonedTracker;
-    }
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers) {
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -113,7 +90,6 @@
     }
 }
 
-
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                                   bool forceStop) {
     VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -252,22 +228,21 @@
         if (pair.second.state == kStopped) {
             continue;
         }
-        std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
         ConditionState conditionState = mWizard->query(
-            mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition,
-            !mSameConditionDimensionsInTracker,
-            !mHasLinksToAllConditionDimensionsInTracker,
-            &conditionDimensionKeySet);
-        bool conditionMet =
-                (conditionState == ConditionState::kTrue) &&
-                (mDimensionInCondition.size() == 0 ||
-                 conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
-                         conditionDimensionKeySet.end());
+            mConditionTrackerIndex, pair.second.conditionKeys,
+            !mHasLinksToAllConditionDimensionsInTracker);
+        bool conditionMet = (conditionState == ConditionState::kTrue);
+
         VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet);
         noteConditionChanged(pair.first, conditionMet, timestamp);
     }
 }
 
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                        const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
     for (auto& pair : mInfos) {
         noteConditionChanged(pair.first, condition, timestamp);
@@ -337,6 +312,20 @@
     fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
 }
 
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 8e8f2cd..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -30,7 +30,7 @@
 public:
     MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
                        sp<ConditionWizard> wizard, int conditionIndex,
-                       const std::vector<Matcher>& dimensionInCondition, bool nesting,
+                       bool nesting,
                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                        int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
                        bool fullLink,
@@ -38,8 +38,6 @@
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
@@ -56,10 +54,19 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // Returns true if at least one of the mInfos is started.
     bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 956383a..0d49bbc 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,27 +26,15 @@
 
 OringDurationTracker::OringDurationTracker(
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex, const vector<Matcher>& dimensionInCondition,
-        bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-        int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
-        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers),
+        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+        int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+        bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
-    if (mWizard != nullptr) {
-        mSameConditionDimensionsInTracker =
-            mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
-    }
-}
-
-unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<OringDurationTracker>(*this);
-    clonedTracker->mLastStartTime = eventTime;
-    clonedTracker->mDuration = 0;
-    return clonedTracker;
 }
 
 bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -101,10 +89,14 @@
             mConditionKeyMap.erase(key);
         }
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
-            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
-                 (long long)mDuration);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
         }
     }
 
@@ -123,10 +115,14 @@
 
 void OringDurationTracker::noteStopAll(const int64_t timestamp) {
     if (!mStarted.empty()) {
-        mDuration += (timestamp - mLastStartTime);
-        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
-             (long long)mDuration);
-        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+             (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+             mEventKey.getStateValuesKey().toString().c_str());
+        detectAndDeclareAnomaly(
+                timestamp, mCurrentBucketNum,
+                getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
     }
 
     stopAnomalyAlarm(timestamp);
@@ -157,21 +153,36 @@
 
     // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (currentBucketEndTimeNs - mLastStartTime);
+        // Calculate the duration for the current state key.
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (currentBucketEndTimeNs - mLastStartTime);
     }
-    if (mDuration > 0) {
-        DurationBucket current_info;
-        current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
-        current_info.mBucketEndNs = currentBucketEndTimeNs;
-        current_info.mDuration = mDuration;
-        (*output)[mEventKey].push_back(current_info);
-        mDurationFullBucket += mDuration;
-        VLOG("  duration: %lld", (long long)current_info.mDuration);
-    }
-    if (eventTimeNs > fullBucketEnd) {
-        // End of full bucket, can send to anomaly tracker now.
-        addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
-        mDurationFullBucket = 0;
+    // Store DurationBucket info for each whatKey, stateKey pair.
+    // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+    // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+    // store durations for each stateKey, so we need to flush the bucket by creating a
+    // DurationBucket for each stateKey.
+    for (auto& durationIt : mStateKeyDurationMap) {
+        if (durationIt.second.mDuration > 0) {
+            DurationBucket current_info;
+            current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+            current_info.mBucketEndNs = currentBucketEndTimeNs;
+            current_info.mDuration = durationIt.second.mDuration;
+            (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+                    .push_back(current_info);
+
+            durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+            VLOG("  duration: %lld", (long long)current_info.mDuration);
+        }
+
+        if (eventTimeNs > fullBucketEnd) {
+            // End of full bucket, can send to anomaly tracker now.
+            addPastBucketToAnomalyTrackers(
+                    MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+                    getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+            durationIt.second.mDurationFullBucket = 0;
+        }
+        durationIt.second.mDuration = 0;
     }
 
     if (mStarted.size() > 0) {
@@ -180,20 +191,19 @@
             info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
             info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
             info.mDuration = mBucketSizeNs;
+            // Full duration buckets are attributed to the current stateKey.
             (*output)[mEventKey].push_back(info);
             // Safe to send these buckets to anomaly tracker since they must be full buckets.
             // If it's a partial bucket, numBucketsForward would be 0.
-            addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+            addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
             VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
         }
     } else {
         if (numBucketsForward >= 2) {
-            addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+            addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
         }
     }
 
-    mDuration = 0;
-
     if (numBucketsForward > 0) {
         mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
         mCurrentBucketNum += numBucketsForward;
@@ -227,17 +237,10 @@
                 ++it;
                 continue;
             }
-            std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
             ConditionState conditionState =
                 mWizard->query(mConditionTrackerIndex, condIt->second,
-                               mDimensionInCondition,
-                               !mSameConditionDimensionsInTracker,
-                               !mHasLinksToAllConditionDimensionsInTracker,
-                               &conditionDimensionKeySet);
-            if (conditionState != ConditionState::kTrue ||
-                (mDimensionInCondition.size() != 0 &&
-                 conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) ==
-                         conditionDimensionKeySet.end())) {
+                               !mHasLinksToAllConditionDimensionsInTracker);
+            if (conditionState != ConditionState::kTrue) {
                 startedToPaused.push_back(*it);
                 it = mStarted.erase(it);
                 VLOG("Key %s started -> paused", key.toString().c_str());
@@ -247,10 +250,14 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
-                 (long long)mDuration);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
 
@@ -262,17 +269,10 @@
                 ++it;
                 continue;
             }
-            std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
             ConditionState conditionState =
                 mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
-                               mDimensionInCondition,
-                               !mSameConditionDimensionsInTracker,
-                               !mHasLinksToAllConditionDimensionsInTracker,
-                               &conditionDimensionKeySet);
-            if (conditionState == ConditionState::kTrue &&
-                (mDimensionInCondition.size() == 0 ||
-                 conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
-                         conditionDimensionKeySet.end())) {
+                               !mHasLinksToAllConditionDimensionsInTracker);
+            if (conditionState == ConditionState::kTrue) {
                 pausedToStarted.push_back(*it);
                 it = mPaused.erase(it);
                 VLOG("Key %s paused -> started", key.toString().c_str());
@@ -313,10 +313,13 @@
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
             mPaused.insert(mStarted.begin(), mStarted.end());
             mStarted.clear();
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
     if (mStarted.empty()) {
@@ -324,6 +327,23 @@
     }
 }
 
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                          const FieldValue& newState) {
+    // Nothing needs to be done on a state change if we have not seen a start
+    // event, the metric is currently not active, or condition is false.
+    // For these cases, no keys are being tracked in mStarted, so update
+    // the current state key and return.
+    if (mStarted.empty()) {
+        updateCurrentStateKey(atomId, newState);
+        return;
+    }
+    // Add the current duration length to the previous state key and then update
+    // the last start time and current state key.
+    mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+    mLastStartTime = timestamp;
+    updateCurrentStateKey(atomId, newState);
+}
+
 int64_t OringDurationTracker::predictAnomalyTimestampNs(
         const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
 
@@ -333,12 +353,13 @@
     // The timestamp of the current bucket end.
     const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
 
-    // The past duration ns for the current bucket.
-    int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+    // The past duration ns for the current bucket of the current stateKey.
+    int64_t currentStateBucketPastNs =
+            getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
 
     // As we move into the future, old buckets get overwritten (so their old data is erased).
     // Sum of past durations. Will change as we overwrite old buckets.
-    int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+    int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
 
     // The refractory period end timestamp for dimension mEventKey.
     const int64_t refractoryPeriodEndNs =
@@ -397,7 +418,7 @@
                     mEventKey,
                     mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
         } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
-            pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+            pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
         }
     }
 
@@ -407,7 +428,34 @@
 void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
     fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
     fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
-    fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+    fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDuration;
+    }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDurationFullBucket;
+    }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+    for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+        if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+            stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+        }
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index e466169..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,16 +28,13 @@
 public:
     OringDurationTracker(const ConfigKey& key, const int64_t& id,
                          const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex, const std::vector<Matcher>& dimensionInCondition,
-                         bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-                         int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-                         bool fullLink,
+                         int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+                         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+                         bool conditionSliced, bool fullLink,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
@@ -47,6 +44,9 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     bool flushCurrentBucket(
             const int64_t& eventTimeNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -58,6 +58,12 @@
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 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.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 45c3c69..8917c36 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -19,24 +19,24 @@
 
 #include "metrics_manager_util.h"
 
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
-#include "../matchers/EventMatcherWizard.h"
-#include "../metrics/CountMetricProducer.h"
-#include "../metrics/DurationMetricProducer.h"
-#include "../metrics/EventMetricProducer.h"
-#include "../metrics/GaugeMetricProducer.h"
-#include "../metrics/ValueMetricProducer.h"
-
-#include "atoms_info.h"
-#include "stats_util.h"
-
 #include <inttypes.h>
 
+#include "FieldValue.h"
+#include "MetricProducer.h"
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/EventMatcherWizard.h"
+#include "matchers/SimpleLogMatchingTracker.h"
+#include "metrics/CountMetricProducer.h"
+#include "metrics/DurationMetricProducer.h"
+#include "metrics/EventMetricProducer.h"
+#include "metrics/GaugeMetricProducer.h"
+#include "metrics/ValueMetricProducer.h"
+#include "state/StateManager.h"
+#include "stats_util.h"
+
 using std::set;
 using std::unordered_map;
 using std::vector;
@@ -136,6 +136,105 @@
     return true;
 }
 
+// Initializes state data structures for a metric.
+// input:
+// [config]: the input config
+// [stateIds]: the slice_by_state ids for this metric
+// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids
+// [allStateGroupMaps]: this map contains the mapping from state ids and state
+//                      values to state group ids for all states
+// output:
+// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states
+// [stateGroupMap]: this map should contain the mapping from states ids and state
+//                      values to state group ids for all states that this metric
+//                      is interested in
+bool handleMetricWithStates(
+        const StatsdConfig& config, const ::google::protobuf::RepeatedField<int64_t>& stateIds,
+        const unordered_map<int64_t, int>& stateAtomIdMap,
+        const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+        vector<int>& slicedStateAtoms,
+        unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) {
+    for (const auto& stateId : stateIds) {
+        auto it = stateAtomIdMap.find(stateId);
+        if (it == stateAtomIdMap.end()) {
+            ALOGW("cannot find State %" PRId64 " in the config", stateId);
+            return false;
+        }
+        int atomId = it->second;
+        slicedStateAtoms.push_back(atomId);
+
+        auto stateIt = allStateGroupMaps.find(stateId);
+        if (stateIt != allStateGroupMaps.end()) {
+            stateGroupMap[atomId] = stateIt->second;
+        }
+    }
+    return true;
+}
+
+bool handleMetricWithStateLink(const FieldMatcher& stateMatcher,
+                               const vector<Matcher>& dimensionsInWhat) {
+    vector<Matcher> stateMatchers;
+    translateFieldMatcher(stateMatcher, &stateMatchers);
+
+    return subsetDimensions(stateMatchers, dimensionsInWhat);
+}
+
+// Validates a metricActivation and populates state.
+// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
+//      to provide the producer with state about its activators and deactivators.
+// Returns false if there are errors.
+bool handleMetricActivation(
+        const StatsdConfig& config,
+        const int64_t metricId,
+        const int metricIndex,
+        const unordered_map<int64_t, int>& metricToActivationMap,
+        const unordered_map<int64_t, int>& logTrackerMap,
+        unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+        unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+        vector<int>& metricsWithActivation,
+        unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap) {
+    // Check if metric has an associated activation
+    auto itr = metricToActivationMap.find(metricId);
+    if (itr == metricToActivationMap.end()) return true;
+
+    int activationIndex = itr->second;
+    const MetricActivation& metricActivation = config.metric_activation(activationIndex);
+
+    for (int i = 0; i < metricActivation.event_activation_size(); i++) {
+        const EventActivation& activation = metricActivation.event_activation(i);
+
+        auto itr = logTrackerMap.find(activation.atom_matcher_id());
+        if (itr == logTrackerMap.end()) {
+            ALOGE("Atom matcher not found for event activation.");
+            return false;
+        }
+
+        ActivationType activationType = (activation.has_activation_type()) ?
+                activation.activation_type() : metricActivation.activation_type();
+        std::shared_ptr<Activation> activationWrapper = std::make_shared<Activation>(
+                activationType, activation.ttl_seconds() * NS_PER_SEC);
+
+        int atomMatcherIndex = itr->second;
+        activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex);
+        eventActivationMap.emplace(atomMatcherIndex, activationWrapper);
+
+        if (activation.has_deactivation_atom_matcher_id()) {
+            itr = logTrackerMap.find(activation.deactivation_atom_matcher_id());
+            if (itr == logTrackerMap.end()) {
+                ALOGE("Atom matcher not found for event deactivation.");
+                return false;
+            }
+            int deactivationAtomMatcherIndex = itr->second;
+            deactivationAtomTrackerToMetricMap[deactivationAtomMatcherIndex].push_back(metricIndex);
+            eventDeactivationMap[deactivationAtomMatcherIndex].push_back(activationWrapper);
+        }
+    }
+
+    metricsWithActivation.push_back(metricIndex);
+    return true;
+}
+
 bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
                      unordered_map<int64_t, int>& logTrackerMap,
                      vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
@@ -182,74 +281,26 @@
     return true;
 }
 
-/**
- * A StateTracker is built from a SimplePredicate which has only "start", and no "stop"
- * or "stop_all". The start must be an atom matcher that matches a state atom. It must
- * have dimension, the dimension must be the state atom's primary fields plus exclusive state
- * field. For example, the StateTracker is used in tracking UidProcessState and ScreenState.
- *
- */
-bool isStateTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
-    // 1. must not have "stop". must have "dimension"
-    if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) {
-        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(
-                simplePredicate.dimensions().field());
-        // 2. must be based on a state atom.
-        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
-            // 3. dimension must be primary fields + state field IN ORDER
-            size_t expectedDimensionCount = it->second.primaryFields.size() + 1;
-            vector<Matcher> dimensions;
-            translateFieldMatcher(simplePredicate.dimensions(), &dimensions);
-            if (dimensions.size() != expectedDimensionCount) {
-                return false;
-            }
-            // 3.1 check the primary fields first.
-            size_t index = 0;
-            for (const auto& field : it->second.primaryFields) {
-                Matcher matcher = getSimpleMatcher(it->first, field);
-                if (!(matcher == dimensions[index])) {
-                    return false;
-                }
-                primaryKeys->push_back(matcher);
-                index++;
-            }
-            Matcher stateFieldMatcher =
-                    getSimpleMatcher(it->first, it->second.exclusiveField);
-            // 3.2 last dimension should be the exclusive field.
-            if (!(dimensions.back() == stateFieldMatcher)) {
-                return false;
-            }
-            return true;
-        }
-    }
-    return false;
-}  // namespace statsd
-
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
                     const unordered_map<int64_t, int>& logTrackerMap,
                     unordered_map<int64_t, int>& conditionTrackerMap,
                     vector<sp<ConditionTracker>>& allConditionTrackers,
-                    unordered_map<int, std::vector<int>>& trackerToConditionMap) {
+                    unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                    vector<ConditionState>& initialConditionCache) {
     vector<Predicate> conditionConfigs;
     const int conditionTrackerCount = config.predicate_size();
     conditionConfigs.reserve(conditionTrackerCount);
     allConditionTrackers.reserve(conditionTrackerCount);
+    initialConditionCache.reserve(conditionTrackerCount);
+    std::fill(initialConditionCache.begin(), initialConditionCache.end(), ConditionState::kUnknown);
 
     for (int i = 0; i < conditionTrackerCount; i++) {
         const Predicate& condition = config.predicate(i);
         int index = allConditionTrackers.size();
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
-                vector<Matcher> primaryKeys;
-                if (isStateTracker(condition.simple_predicate(), &primaryKeys)) {
-                    allConditionTrackers.push_back(new StateTracker(key, condition.id(), index,
-                                                                    condition.simple_predicate(),
-                                                                    logTrackerMap, primaryKeys));
-                } else {
-                    allConditionTrackers.push_back(new SimpleConditionTracker(
-                            key, condition.id(), index, condition.simple_predicate(),
-                            logTrackerMap));
-                }
+                allConditionTrackers.push_back(new SimpleConditionTracker(
+                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
                 break;
             }
             case Predicate::ContentsCase::kCombination: {
@@ -273,7 +324,7 @@
     for (size_t i = 0; i < allConditionTrackers.size(); i++) {
         auto& conditionTracker = allConditionTrackers[i];
         if (!conditionTracker->init(conditionConfigs, allConditionTrackers, conditionTrackerMap,
-                                    stackTracker)) {
+                                    stackTracker, initialConditionCache)) {
             return false;
         }
         for (const int trackerIndex : conditionTracker->getLogTrackerIndex()) {
@@ -284,23 +335,59 @@
     return true;
 }
 
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+                unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps) {
+    for (int i = 0; i < config.state_size(); i++) {
+        const State& state = config.state(i);
+        const int64_t stateId = state.id();
+        stateAtomIdMap[stateId] = state.atom_id();
+
+        const StateMap& stateMap = state.map();
+        for (auto group : stateMap.group()) {
+            for (auto value : group.value()) {
+                allStateGroupMaps[stateId][value] = group.group_id();
+            }
+        }
+    }
+
+    return true;
+}
+
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
-                 const int64_t currentTimeNs,
-                 const sp<StatsPullerManager>& pullerManager,
+                 const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
                  const unordered_map<int64_t, int>& logTrackerMap,
                  const unordered_map<int64_t, int>& conditionTrackerMap,
                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                 const unordered_map<int64_t, int>& stateAtomIdMap,
+                 const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
+                 const vector<ConditionState>& initialConditionCache,
                  vector<sp<MetricProducer>>& allMetricProducers,
-                 unordered_map<int, std::vector<int>>& conditionToMetricMap,
-                 unordered_map<int, std::vector<int>>& trackerToMetricMap,
-                 unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds) {
+                 unordered_map<int, vector<int>>& conditionToMetricMap,
+                 unordered_map<int, vector<int>>& trackerToMetricMap,
+                 unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds,
+                 unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+                 unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+                 vector<int>& metricsWithActivation) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
-                                config.event_metric_size() + config.value_metric_size();
+                                config.event_metric_size() + config.gauge_metric_size() +
+                                config.value_metric_size();
     allMetricProducers.reserve(allMetricsCount);
-    StatsPullerManager statsPullerManager;
+
+    // Construct map from metric id to metric activation index. The map will be used to determine
+    // the metric activation corresponding to a metric.
+    unordered_map<int64_t, int> metricToActivationMap;
+    for (int i = 0; i < config.metric_activation_size(); i++) {
+        const MetricActivation& metricActivation = config.metric_activation(i);
+        int64_t metricId = metricActivation.metric_id();
+        if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) {
+            ALOGE("Metric %lld has multiple MetricActivations", (long long) metricId);
+            return false;
+        }
+        metricToActivationMap.insert({metricId, i});
+    }
 
     // Build MetricProducers for each metric defined in config.
     // build CountMetricProducer
@@ -323,10 +410,9 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            bool good = handleMetricWithConditions(
-                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
-                    allConditionTrackers, conditionIndex, conditionToMetricMap);
-            if (!good) {
+            if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                            metric.links(), allConditionTrackers, conditionIndex,
+                                            conditionToMetricMap)) {
                 return false;
             }
         } else {
@@ -336,8 +422,32 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state");
+                return false;
+            }
+        }
+
+        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+        bool success = handleMetricActivation(config, metric.id(), metricIndex,
+                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+                eventDeactivationMap);
+        if (!success) return false;
+
         sp<MetricProducer> countProducer =
-                new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs);
+                new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+                                        timeBaseTimeNs, currentTimeNs, eventActivationMap,
+                                        eventDeactivationMap, slicedStateAtoms, stateGroupMap);
         allMetricProducers.push_back(countProducer);
     }
 
@@ -405,9 +515,46 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+                ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+                return false;
+            }
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+                return false;
+            }
+        }
+
+        // Check that all metric state links are a subset of dimensions_in_what fields.
+        std::vector<Matcher> dimensionsInWhat;
+        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+        for (const auto& stateLink : metric.state_link()) {
+            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+                return false;
+            }
+        }
+
+        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+        bool success = handleMetricActivation(config, metric.id(), metricIndex,
+                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+                eventDeactivationMap);
+        if (!success) return false;
+
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
-                key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
-                trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, currentTimeNs);
+                key, metric, conditionIndex, initialConditionCache, trackerIndices[0],
+                trackerIndices[1], trackerIndices[2], nesting, wizard, internalDimensions,
+                timeBaseTimeNs, currentTimeNs, eventActivationMap, eventDeactivationMap,
+                slicedStateAtoms, stateGroupMap);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -442,8 +589,17 @@
             }
         }
 
+        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+        bool success = handleMetricActivation(config, metric.id(), metricIndex,
+                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+                eventDeactivationMap);
+        if (!success) return false;
+
         sp<MetricProducer> eventMetric =
-                new EventMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs);
+                new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+                                        timeBaseTimeNs, eventActivationMap, eventDeactivationMap);
 
         allMetricProducers.push_back(eventMetric);
     }
@@ -482,7 +638,7 @@
             return false;
         }
         int atomTagId = *(atomMatcher->getAtomIds().begin());
-        int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
+        int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
@@ -499,9 +655,41 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("ValueMetric has a MetricStateLink but doesn't have a sliced state");
+                return false;
+            }
+        }
+
+        // Check that all metric state links are a subset of dimensions_in_what fields.
+        std::vector<Matcher> dimensionsInWhat;
+        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+        for (const auto& stateLink : metric.state_link()) {
+            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+                return false;
+            }
+        }
+
+        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
+        if (!success) return false;
+
         sp<MetricProducer> valueProducer = new ValueMetricProducer(
-                key, metric, conditionIndex, wizard, trackerIndex, matcherWizard, pullTagId,
-                timeBaseTimeNs, currentTimeNs, pullerManager);
+                key, metric, conditionIndex, initialConditionCache, wizard, trackerIndex,
+                matcherWizard, pullTagId, timeBaseTimeNs, currentTimeNs, pullerManager,
+                eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap);
         allMetricProducers.push_back(valueProducer);
     }
 
@@ -542,7 +730,7 @@
             return false;
         }
         int atomTagId = *(atomMatcher->getAtomIds().begin());
-        int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
+        int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int triggerTrackerIndex;
         int triggerAtomId = -1;
@@ -585,10 +773,18 @@
             }
         }
 
+        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+        bool success = handleMetricActivation(config, metric.id(), metricIndex,
+                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+                eventDeactivationMap);
+        if (!success) return false;
+
         sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
-                key, metric, conditionIndex, wizard,
-                trackerIndex, matcherWizard, pullTagId, triggerAtomId, atomTagId,
-                timeBaseTimeNs, currentTimeNs, pullerManager);
+                key, metric, conditionIndex, initialConditionCache, wizard, trackerIndex,
+                matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseTimeNs, currentTimeNs,
+                pullerManager, eventActivationMap, eventDeactivationMap);
         allMetricProducers.push_back(gaugeProducer);
     }
     for (int i = 0; i < config.no_report_metric_size(); ++i) {
@@ -599,15 +795,30 @@
         }
         noReportMetricIds.insert(no_report_metric);
     }
+
+    const set<int> whitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+                                      config.whitelisted_atom_ids().end());
+    for (const auto& it : allMetricProducers) {
+        // Register metrics to StateTrackers
+        for (int atomId : it->getSlicedStateAtoms()) {
+            // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced
+            // state atom is not allowed.
+            if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) {
+                StateManager::getInstance().registerListener(atomId, it);
+            } else {
+                return false;
+            }
+        }
+    }
     return true;
 }
 
 bool initAlerts(const StatsdConfig& config,
                 const unordered_map<int64_t, int>& metricProducerMap,
+                unordered_map<int64_t, int>& alertTrackerMap,
                 const sp<AlarmMonitor>& anomalyAlarmMonitor,
                 vector<sp<MetricProducer>>& allMetricProducers,
                 vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
-    unordered_map<int64_t, int> anomalyTrackerMap;
     for (int i = 0; i < config.alert_size(); i++) {
         const Alert& alert = config.alert(i);
         const auto& itr = metricProducerMap.find(alert.metric_id());
@@ -632,7 +843,7 @@
             // The ALOGW for this invalid alert was already displayed in addAnomalyTracker().
             return false;
         }
-        anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size()));
+        alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size()));
         allAnomalyTrackers.push_back(anomalyTracker);
     }
     for (int i = 0; i < config.subscription_size(); ++i) {
@@ -646,8 +857,8 @@
                 (long long)subscription.id());
             return false;
         }
-        const auto& itr = anomalyTrackerMap.find(subscription.rule_id());
-        if (itr == anomalyTrackerMap.end()) {
+        const auto& itr = alertTrackerMap.find(subscription.rule_id());
+        if (itr == alertTrackerMap.end()) {
             ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
                 (long long)subscription.id(), (long long)subscription.rule_id());
             return false;
@@ -703,73 +914,6 @@
     return true;
 }
 
-bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config,
-                           const int64_t currentTimeNs,
-                           const unordered_map<int64_t, int> &logEventTrackerMap,
-                           const unordered_map<int64_t, int> &metricProducerMap,
-                           vector<sp<MetricProducer>>& allMetricProducers,
-                           unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
-                           unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-                           vector<int>& metricsWithActivation) {
-    for (int i = 0; i < config.metric_activation_size(); ++i) {
-        const MetricActivation& metric_activation = config.metric_activation(i);
-        auto itr = metricProducerMap.find(metric_activation.metric_id());
-        if (itr == metricProducerMap.end()) {
-            ALOGE("Metric id not found in metric activation: %lld",
-                (long long)metric_activation.metric_id());
-            return false;
-        }
-        const int metricTrackerIndex = itr->second;
-        if (metricTrackerIndex < 0 || metricTrackerIndex >= (int)allMetricProducers.size()) {
-            ALOGE("Invalid metric tracker index.");
-            return false;
-        }
-        const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex];
-        metricsWithActivation.push_back(metricTrackerIndex);
-        for (int j = 0; j < metric_activation.event_activation_size(); ++j) {
-            const EventActivation& activation = metric_activation.event_activation(j);
-            auto logTrackerIt = logEventTrackerMap.find(activation.atom_matcher_id());
-            if (logTrackerIt == logEventTrackerMap.end()) {
-                ALOGE("Atom matcher not found for event activation.");
-                return false;
-            }
-            const int atomMatcherIndex = logTrackerIt->second;
-            activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(
-                metricTrackerIndex);
-
-            ActivationType activationType;
-            if (activation.has_activation_type()) {
-                activationType = activation.activation_type();
-            } else {
-                activationType = metric_activation.activation_type();
-            }
-
-            if (activation.has_deactivation_atom_matcher_id()) {
-                auto deactivationAtomMatcherIt =
-                        logEventTrackerMap.find(activation.deactivation_atom_matcher_id());
-                if (deactivationAtomMatcherIt == logEventTrackerMap.end()) {
-                    ALOGE("Atom matcher not found for event deactivation.");
-                    return false;
-                }
-                const int deactivationMatcherIndex = deactivationAtomMatcherIt->second;
-                deactivationAtomTrackerToMetricMap[deactivationMatcherIndex]
-                        .push_back(metricTrackerIndex);
-                metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds(),
-                                      deactivationMatcherIndex);
-            } else {
-                metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds());
-            }
-        }
-    }
-    return true;
-}
-
-void prepareFirstBucket(const vector<sp<MetricProducer>>& allMetricProducers) {
-    for (const auto& metric: allMetricProducers) {
-        metric->prepareFirstBucket();
-    }
-}
-
 bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
                       const sp<StatsPullerManager>& pullerManager,
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -785,11 +929,15 @@
                       unordered_map<int, std::vector<int>>& trackerToConditionMap,
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+                      unordered_map<int64_t, int>& alertTrackerMap,
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds) {
     unordered_map<int64_t, int> logTrackerMap;
     unordered_map<int64_t, int> conditionTrackerMap;
+    vector<ConditionState> initialConditionCache;
     unordered_map<int64_t, int> metricProducerMap;
+    unordered_map<int64_t, int> stateAtomIdMap;
+    unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
 
     if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
         ALOGE("initLogMatchingTrackers failed");
@@ -798,20 +946,26 @@
     VLOG("initLogMatchingTrackers succeed...");
 
     if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
-                        trackerToConditionMap)) {
+                        trackerToConditionMap, initialConditionCache)) {
         ALOGE("initConditionTrackers failed");
         return false;
     }
 
+    if (!initStates(config, stateAtomIdMap, allStateGroupMaps)) {
+        ALOGE("initStates failed");
+        return false;
+    }
     if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap,
-                     conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
-                     conditionToMetricMap, trackerToMetricMap, metricProducerMap,
-                     noReportMetricIds)) {
+                     conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
+                     allConditionTrackers, initialConditionCache, allMetricProducers,
+                     conditionToMetricMap, trackerToMetricMap, metricProducerMap, noReportMetricIds,
+                     activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                     metricsWithActivation)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
-    if (!initAlerts(config, metricProducerMap, anomalyAlarmMonitor, allMetricProducers,
-                    allAnomalyTrackers)) {
+    if (!initAlerts(config, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor,
+                    allMetricProducers, allAnomalyTrackers)) {
         ALOGE("initAlerts failed");
         return false;
     }
@@ -820,14 +974,6 @@
         ALOGE("initAlarms failed");
         return false;
     }
-    if (!initMetricActivations(key, config, currentTimeNs, logTrackerMap, metricProducerMap,
-            allMetricProducers, activationAtomTrackerToMetricMap,
-            deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
-        ALOGE("initMetricActivations failed");
-        return false;
-    }
-
-    prepareFirstBucket(allMetricProducers);
 
     return true;
 }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index ece986b..96b5c26 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -60,12 +60,25 @@
 // [allConditionTrackers]: stores the sp to all the ConditionTrackers
 // [trackerToConditionMap]: contain the mapping from index of
 //                        log tracker to condition trackers that use the log tracker
+// [initialConditionCache]: stores the initial conditions for each ConditionTracker
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
                     const std::unordered_map<int64_t, int>& logTrackerMap,
                     std::unordered_map<int64_t, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
                     std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
-                    std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
+                    std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
+                    std::vector<ConditionState>& initialConditionCache);
+
+// Initialize State maps using State protos in the config. These maps will
+// eventually be passed to MetricProducers to initialize their state info.
+// input:
+// [config]: the input config
+// output:
+// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids
+// [allStateGroupMaps]: this map should contain the mapping from states ids and state
+//                      values to state group ids for all states
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+                unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps);
 
 // Initialize MetricProducers.
 // input:
@@ -74,6 +87,9 @@
 // [timeBaseSec]: start time base for all metrics
 // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+//                      state group ids for all states
 // output:
 // [allMetricProducers]: contains the list of sp to the MetricProducers created.
 // [conditionToMetricMap]: contains the mapping from condition tracker index to
@@ -86,11 +102,17 @@
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
         const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+        const unordered_map<int64_t, int>& stateAtomIdMap,
+        const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         vector<sp<ConditionTracker>>& allConditionTrackers,
+        const std::vector<ConditionState>& initialConditionCache,
         std::vector<sp<MetricProducer>>& allMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
-        std::set<int64_t>& noReportMetricIds);
+        std::set<int64_t>& noReportMetricIds,
+        std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+        std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+        std::vector<int>& metricsWithActivation);
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -109,11 +131,10 @@
                       std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+                      std::unordered_map<int64_t, int>& alertTrackerMap,
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds);
 
-bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index ab0e86e..acf40c8 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -545,7 +545,15 @@
                                                              {"AID_LMKD", 1069},
                                                              {"AID_LLKD", 1070},
                                                              {"AID_IORAPD", 1071},
+                                                             {"AID_GPU_SERVICE", 1072},
                                                              {"AID_NETWORK_STACK", 1073},
+                                                             {"AID_GSID", 1074},
+                                                             {"AID_FSVERITY_CERT", 1075},
+                                                             {"AID_CREDSTORE", 1076},
+                                                             {"AID_EXTERNAL_STORAGE", 1077},
+                                                             {"AID_EXT_DATA_RW", 1078},
+                                                             {"AID_EXT_OBB_RW", 1079},
+                                                             {"AID_CONTEXT_HUB", 1080},
                                                              {"AID_SHELL", 2000},
                                                              {"AID_CACHE", 2001},
                                                              {"AID_DIAG", 2002}};
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index bfac6e3..22250ae 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -21,10 +21,11 @@
 #include "packages/PackageInfoListener.h"
 #include "stats_util.h"
 
-#include <binder/IShellCallback.h>
 #include <gtest/gtest_prod.h>
 #include <stdio.h>
 #include <utils/RefBase.h>
+#include <utils/String16.h>
+
 #include <list>
 #include <mutex>
 #include <set>
@@ -138,7 +139,7 @@
     // record is deleted.
     void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set,
                       bool includeVersionStrings, bool includeInstaller,
-                      util::ProtoOutputStream* proto);
+                      ProtoOutputStream* proto);
 
     // 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
@@ -148,7 +149,7 @@
     // Get currently cached value of memory used by UID map.
     size_t getBytesUsed() const;
 
-    std::set<int32_t> getAppUid(const string& package) const;
+    virtual std::set<int32_t> getAppUid(const string& package) const;
 
     // Write current PackageInfoSnapshot to ProtoOutputStream.
     // interestingUids: If not empty, only write the package info for these uids. If empty, write
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index d6a0433..fd883c2 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -18,6 +18,8 @@
 
 #include "ShellSubscriber.h"
 
+#include <android-base/file.h>
+
 #include "matchers/matcher_util.h"
 #include "stats_log_util.h"
 
@@ -29,82 +31,167 @@
 
 const static int FIELD_ID_ATOM = 1;
 
-void ShellSubscriber::startNewSubscription(int in, int out, sp<IResultReceiver> resultReceiver,
-                                           int timeoutSec) {
-    VLOG("start new shell subscription");
+void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) {
+    int myToken = claimToken();
+    VLOG("ShellSubscriber: new subscription %d has come in", myToken);
+    mSubscriptionShouldEnd.notify_one();
+
+    shared_ptr<SubscriptionInfo> mySubscriptionInfo = make_shared<SubscriptionInfo>(in, out);
+    if (!readConfig(mySubscriptionInfo)) return;
+
     {
-        std::lock_guard<std::mutex> lock(mMutex);
-        if (mResultReceiver != nullptr) {
-            VLOG("Only one shell subscriber is allowed.");
-            return;
+        std::unique_lock<std::mutex> lock(mMutex);
+        mSubscriptionInfo = mySubscriptionInfo;
+        spawnHelperThread(myToken);
+        waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec);
+
+        if (mSubscriptionInfo == mySubscriptionInfo) {
+            mSubscriptionInfo = nullptr;
         }
-        mInput = in;
-        mOutput = out;
-        mResultReceiver = resultReceiver;
-        IInterface::asBinder(mResultReceiver)->linkToDeath(this);
-    }
 
-    // Note that the following is blocking, and it's intended as we cannot return until the shell
-    // cmd exits, otherwise all resources & FDs will be automatically closed.
-
-    // Read config forever until EOF is reached. Clients may send multiple configs -- each new
-    // config replace the previous one.
-    readConfig(in);
-    VLOG("timeout : %d", timeoutSec);
-
-    // Now we have read an EOF we now wait for the semaphore until the client exits.
-    VLOG("Now wait for client to exit");
-    std::unique_lock<std::mutex> lk(mMutex);
-
-    if (timeoutSec > 0) {
-        mShellDied.wait_for(lk, timeoutSec * 1s,
-                            [this, resultReceiver] { return mResultReceiver != resultReceiver; });
-    } else {
-        mShellDied.wait(lk, [this, resultReceiver] { return mResultReceiver != resultReceiver; });
     }
 }
 
-void ShellSubscriber::updateConfig(const ShellSubscription& config) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    mPushedMatchers.clear();
-    mPulledInfo.clear();
+void ShellSubscriber::spawnHelperThread(int myToken) {
+    std::thread t([this, myToken] { pullAndSendHeartbeats(myToken); });
+    t.detach();
+}
 
-    for (const auto& pushed : config.pushed()) {
-        mPushedMatchers.push_back(pushed);
-        VLOG("adding matcher for atom %d", pushed.atom_id());
+void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr<SubscriptionInfo> myInfo,
+                                                     int myToken,
+                                                     std::unique_lock<std::mutex>& lock,
+                                                     int timeoutSec) {
+    if (timeoutSec > 0) {
+        mSubscriptionShouldEnd.wait_for(lock, timeoutSec * 1s, [this, myToken, &myInfo] {
+            return mToken != myToken || !myInfo->mClientAlive;
+        });
+    } else {
+        mSubscriptionShouldEnd.wait(lock, [this, myToken, &myInfo] {
+            return mToken != myToken || !myInfo->mClientAlive;
+        });
+    }
+}
+
+// Atomically claim the next token. Token numbers denote subscriber ordering.
+int ShellSubscriber::claimToken() {
+    std::unique_lock<std::mutex> lock(mMutex);
+    int myToken = ++mToken;
+    return myToken;
+}
+
+// Read and parse single config. There should only one config per input.
+bool ShellSubscriber::readConfig(shared_ptr<SubscriptionInfo> subscriptionInfo) {
+    // Read the size of the config.
+    size_t bufferSize;
+    if (!android::base::ReadFully(subscriptionInfo->mInputFd, &bufferSize, sizeof(bufferSize))) {
+        return false;
     }
 
-    int64_t token = getElapsedRealtimeNs();
-    mPullToken = token;
+    // Read the config.
+    vector<uint8_t> buffer(bufferSize);
+    if (!android::base::ReadFully(subscriptionInfo->mInputFd, buffer.data(), bufferSize)) {
+        return false;
+    }
 
-    int64_t minInterval = -1;
+    // Parse the config.
+    ShellSubscription config;
+    if (!config.ParseFromArray(buffer.data(), bufferSize)) {
+        return false;
+    }
+
+    // Update SubscriptionInfo with state from config
+    for (const auto& pushed : config.pushed()) {
+        subscriptionInfo->mPushedMatchers.push_back(pushed);
+    }
+
     for (const auto& pulled : config.pulled()) {
-        // All intervals need to be multiples of the min interval.
-        if (minInterval < 0 || pulled.freq_millis() < minInterval) {
-            minInterval = pulled.freq_millis();
+        vector<string> packages;
+        vector<int32_t> uids;
+        for (const string& pkg : pulled.packages()) {
+            auto it = UidMap::sAidToUidMapping.find(pkg);
+            if (it != UidMap::sAidToUidMapping.end()) {
+                uids.push_back(it->second);
+            } else {
+                packages.push_back(pkg);
+            }
         }
 
-        mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis());
+        subscriptionInfo->mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages,
+                                                   uids);
         VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id());
     }
 
-    if (mPulledInfo.size() > 0 && minInterval > 0) {
-        // This thread is guaranteed to terminate after it detects the token is different or
-        // cleaned up.
-        std::thread puller([token, minInterval, this] { startPull(token, minInterval); });
-        puller.detach();
+    return true;
+}
+
+void ShellSubscriber::pullAndSendHeartbeats(int myToken) {
+    VLOG("ShellSubscriber: helper thread %d starting", myToken);
+    while (true) {
+        int64_t sleepTimeMs = INT_MAX;
+        {
+            std::lock_guard<std::mutex> lock(mMutex);
+            if (!mSubscriptionInfo || mToken != myToken) {
+                VLOG("ShellSubscriber: helper thread %d done!", myToken);
+                return;
+            }
+
+            int64_t nowMillis = getElapsedRealtimeMillis();
+            int64_t nowNanos = getElapsedRealtimeNs();
+            for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) {
+                if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) {
+                    continue;
+                }
+
+                vector<int32_t> uids;
+                getUidsForPullAtom(&uids, pullInfo);
+
+                vector<std::shared_ptr<LogEvent>> data;
+                mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data);
+                VLOG("Pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id());
+                writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
+
+                pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
+            }
+
+            // Send a heartbeat, consisting of a data size of 0, if perfd hasn't recently received
+            // data from statsd. When it receives the data size of 0, perfd will not expect any
+            // atoms and recheck whether the subscription should end.
+            if (nowMillis - mLastWriteMs > kMsBetweenHeartbeats) {
+                attemptWriteToPipeLocked(/*dataSize=*/0);
+            }
+
+            // Determine how long to sleep before doing more work.
+            for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) {
+                int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval;
+                int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative
+                if (timeBeforePull < sleepTimeMs) sleepTimeMs = timeBeforePull;
+            }
+            int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis;
+            if (timeBeforeHeartbeat < sleepTimeMs) sleepTimeMs = timeBeforeHeartbeat;
+        }
+
+        VLOG("ShellSubscriber: helper thread %d sleeping for %lld ms", myToken,
+             (long long)sleepTimeMs);
+        std::this_thread::sleep_for(std::chrono::milliseconds(sleepTimeMs));
     }
 }
 
-void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
-                                          const SimpleAtomMatcher& matcher) {
-    if (mOutput == 0) return;
-    int count = 0;
+void ShellSubscriber::getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo) {
+    uids->insert(uids->end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end());
+    // This is slow. Consider storing the uids per app and listening to uidmap updates.
+    for (const string& pkg : pullInfo.mPullPackages) {
+        set<int32_t> uidsForPkg = mUidMap->getAppUid(pkg);
+        uids->insert(uids->end(), uidsForPkg.begin(), uidsForPkg.end());
+    }
+    uids->push_back(DEFAULT_PULL_UID);
+}
+
+void ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+                                             const SimpleAtomMatcher& matcher) {
     mProto.clear();
+    int count = 0;
     for (const auto& event : data) {
-        VLOG("%s", event->ToString().c_str());
         if (matchesSimple(*mUidMap, matcher, *event)) {
-            VLOG("matched");
             count++;
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
@@ -113,120 +200,44 @@
         }
     }
 
-    if (count > 0) {
-        // First write the payload size.
-        size_t bufferSize = mProto.size();
-        write(mOutput, &bufferSize, sizeof(bufferSize));
-        VLOG("%d atoms, proto size: %zu", count, bufferSize);
-        // Then write the payload.
-        mProto.flush(mOutput);
-    }
-    mProto.clear();
-}
-
-void ShellSubscriber::startPull(int64_t token, int64_t intervalMillis) {
-    while (1) {
-        int64_t nowMillis = getElapsedRealtimeMillis();
-        {
-            std::lock_guard<std::mutex> lock(mMutex);
-            if (mPulledInfo.size() == 0 || mPullToken != token) {
-                VLOG("Pulling thread %lld done!", (long long)token);
-                return;
-            }
-            for (auto& pullInfo : mPulledInfo) {
-                if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval < nowMillis) {
-                    VLOG("pull atom %d now", pullInfo.mPullerMatcher.atom_id());
-
-                    vector<std::shared_ptr<LogEvent>> data;
-                    mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), &data);
-                    VLOG("pulled %zu atoms", data.size());
-                    if (data.size() > 0) {
-                        writeToOutputLocked(data, pullInfo.mPullerMatcher);
-                    }
-                    pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
-                }
-            }
-        }
-        VLOG("Pulling thread %lld sleep....", (long long)token);
-        std::this_thread::sleep_for(std::chrono::milliseconds(intervalMillis));
-    }
-}
-
-void ShellSubscriber::readConfig(int in) {
-    if (in <= 0) {
-        return;
-    }
-
-    while (1) {
-        size_t bufferSize = 0;
-        int result = 0;
-        if ((result = read(in, &bufferSize, sizeof(bufferSize))) == 0) {
-            VLOG("Done reading");
-            break;
-        } else if (result < 0 || result != sizeof(bufferSize)) {
-            ALOGE("Error reading config size");
-            break;
-        }
-
-        vector<uint8_t> buffer(bufferSize);
-        if ((result = read(in, buffer.data(), bufferSize)) > 0 && ((size_t)result) == bufferSize) {
-            ShellSubscription config;
-            if (config.ParseFromArray(buffer.data(), bufferSize)) {
-                updateConfig(config);
-            } else {
-                ALOGE("error parsing the config");
-                break;
-            }
-        } else {
-            VLOG("Error reading the config, returned: %d, expecting %zu", result, bufferSize);
-            break;
-        }
-    }
-}
-
-void ShellSubscriber::cleanUpLocked() {
-    // The file descriptors will be closed by binder.
-    mInput = 0;
-    mOutput = 0;
-    mResultReceiver = nullptr;
-    mPushedMatchers.clear();
-    mPulledInfo.clear();
-    mPullToken = 0;
-    VLOG("done clean up");
+    if (count > 0) attemptWriteToPipeLocked(mProto.size());
 }
 
 void ShellSubscriber::onLogEvent(const LogEvent& event) {
     std::lock_guard<std::mutex> lock(mMutex);
+    if (!mSubscriptionInfo) return;
 
-    if (mOutput <= 0) {
-        return;
-    }
-    for (const auto& matcher : mPushedMatchers) {
+    mProto.clear();
+    for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) {
         if (matchesSimple(*mUidMap, matcher, event)) {
-            VLOG("%s", event.ToString().c_str());
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
             event.ToProto(mProto);
             mProto.end(atomToken);
-            // First write the payload size.
-            size_t bufferSize = mProto.size();
-            write(mOutput, &bufferSize, sizeof(bufferSize));
-
-            // Then write the payload.
-            mProto.flush(mOutput);
-            mProto.clear();
-            break;
+            attemptWriteToPipeLocked(mProto.size());
         }
     }
 }
 
-void ShellSubscriber::binderDied(const wp<IBinder>& who) {
-    {
-        VLOG("Shell exits");
-        std::lock_guard<std::mutex> lock(mMutex);
-        cleanUpLocked();
+// Tries to write the atom encoded in mProto to the pipe. If the write fails
+// because the read end of the pipe has closed, signals to other threads that
+// the subscription should end.
+void ShellSubscriber::attemptWriteToPipeLocked(size_t dataSize) {
+    // First, write the payload size.
+    if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) {
+        mSubscriptionInfo->mClientAlive = false;
+        mSubscriptionShouldEnd.notify_one();
+        return;
     }
-    mShellDied.notify_all();
+
+    // Then, write the payload if this is not just a heartbeat.
+    if (dataSize > 0 && !mProto.flush(mSubscriptionInfo->mOutputFd)) {
+        mSubscriptionInfo->mClientAlive = false;
+        mSubscriptionShouldEnd.notify_one();
+        return;
+    }
+
+    mLastWriteMs = getElapsedRealtimeMillis();
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h
index 86d8590..4c05fa7 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.h
+++ b/cmds/statsd/src/shell/ShellSubscriber.h
@@ -16,16 +16,17 @@
 
 #pragma once
 
-#include "logd/LogEvent.h"
-
 #include <android/util/ProtoOutputStream.h>
-#include <binder/IResultReceiver.h>
+#include <private/android_filesystem_config.h>
+
 #include <condition_variable>
 #include <mutex>
 #include <thread>
+
 #include "external/StatsPullerManager.h"
 #include "frameworks/base/cmds/statsd/src/shell/shell_config.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "logd/LogEvent.h"
 #include "packages/UidMap.h"
 
 namespace android {
@@ -37,11 +38,11 @@
  *
  * A shell subscription lasts *until shell exits*. Unlike config based clients, a shell client
  * communicates with statsd via file descriptors. They can subscribe pushed and pulled atoms.
- * The atoms are sent back to the client in real time, as opposed to
- * keeping the data in memory. Shell clients do not subscribe aggregated metrics, as they are
- * responsible for doing the aggregation after receiving the atom events.
+ * The atoms are sent back to the client in real time, as opposed to keeping the data in memory.
+ * Shell clients do not subscribe aggregated metrics, as they are responsible for doing the
+ * aggregation after receiving the atom events.
  *
- * Shell client pass ShellSubscription in the proto binary format. Client can update the
+ * Shell clients pass ShellSubscription in the proto binary format. Clients can update the
  * subscription by sending a new subscription. The new subscription would replace the old one.
  * Input data stream format is:
  *
@@ -53,43 +54,70 @@
  * The stream would be in the following format:
  * |size_t|shellData proto|size_t|shellData proto|....
  *
- * Only one shell subscriber allowed at a time, because each shell subscriber blocks one thread
+ * Only one shell subscriber is allowed at a time because each shell subscriber blocks one thread
  * until it exits.
  */
-class ShellSubscriber : public virtual IBinder::DeathRecipient {
+class ShellSubscriber : public virtual RefBase {
 public:
     ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
         : mUidMap(uidMap), mPullerMgr(pullerMgr){};
 
-    /**
-     * Start a new subscription.
-     */
-    void startNewSubscription(int inFd, int outFd, sp<IResultReceiver> resultReceiver,
-                              int timeoutSec);
-
-    void binderDied(const wp<IBinder>& who);
+    void startNewSubscription(int inFd, int outFd, int timeoutSec);
 
     void onLogEvent(const LogEvent& event);
 
 private:
     struct PullInfo {
-        PullInfo(const SimpleAtomMatcher& matcher, int64_t interval)
-            : mPullerMatcher(matcher), mInterval(interval), mPrevPullElapsedRealtimeMs(0) {
+        PullInfo(const SimpleAtomMatcher& matcher, int64_t interval,
+                 const std::vector<std::string>& packages, const std::vector<int32_t>& uids)
+            : mPullerMatcher(matcher),
+              mInterval(interval),
+              mPrevPullElapsedRealtimeMs(0),
+              mPullPackages(packages),
+              mPullUids(uids) {
         }
         SimpleAtomMatcher mPullerMatcher;
         int64_t mInterval;
         int64_t mPrevPullElapsedRealtimeMs;
+        std::vector<std::string> mPullPackages;
+        std::vector<int32_t> mPullUids;
     };
-    void readConfig(int in);
 
-    void updateConfig(const ShellSubscription& config);
+    struct SubscriptionInfo {
+        SubscriptionInfo(const int& inputFd, const int& outputFd)
+            : mInputFd(inputFd), mOutputFd(outputFd), mClientAlive(true) {
+        }
 
-    void startPull(int64_t token, int64_t intervalMillis);
+        int mInputFd;
+        int mOutputFd;
+        std::vector<SimpleAtomMatcher> mPushedMatchers;
+        std::vector<PullInfo> mPulledInfo;
+        bool mClientAlive;
+    };
 
-    void cleanUpLocked();
+    int claimToken();
 
-    void writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
-                             const SimpleAtomMatcher& matcher);
+    bool readConfig(std::shared_ptr<SubscriptionInfo> subscriptionInfo);
+
+    void spawnHelperThread(int myToken);
+
+    void waitForSubscriptionToEndLocked(std::shared_ptr<SubscriptionInfo> myInfo,
+                                        int myToken,
+                                        std::unique_lock<std::mutex>& lock,
+                                        int timeoutSec);
+
+    // Helper thread that pulls atoms at a regular frequency and sends
+    // heartbeats to perfd if statsd hasn't recently sent any data. Statsd must
+    // send heartbeats for perfd to escape a blocking read call and recheck if
+    // the user has terminated the subscription.
+    void pullAndSendHeartbeats(int myToken);
+
+    void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+                                const SimpleAtomMatcher& matcher);
+
+    void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo);
+
+    void attemptWriteToPipeLocked(size_t dataSize);
 
     sp<UidMap> mUidMap;
 
@@ -99,19 +127,18 @@
 
     mutable std::mutex mMutex;
 
-    std::condition_variable mShellDied;  // semaphore for waiting until shell exits.
+    std::condition_variable mSubscriptionShouldEnd;
 
-    int mInput;  // The input file descriptor
+    std::shared_ptr<SubscriptionInfo> mSubscriptionInfo = nullptr;
 
-    int mOutput;  // The output file descriptor
+    int mToken = 0;
 
-    sp<IResultReceiver> mResultReceiver;
+    const int32_t DEFAULT_PULL_UID = AID_SYSTEM;
 
-    std::vector<SimpleAtomMatcher> mPushedMatchers;
-
-    std::vector<PullInfo> mPulledInfo;
-
-    int64_t mPullToken = 0;  // A unique token to identify a puller thread.
+    // Tracks when we last send data to perfd. We need that time to determine
+    // when next to send a heartbeat.
+    int64_t mLastWriteMs = 0;
+    const int64_t kMsBetweenHeartbeats = 1000;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/shell/shell_config.proto b/cmds/statsd/src/shell/shell_config.proto
index 73cb49a..07d0310 100644
--- a/cmds/statsd/src/shell/shell_config.proto
+++ b/cmds/statsd/src/shell/shell_config.proto
@@ -28,6 +28,9 @@
 
     /* gap between two pulls in milliseconds */
     optional int32 freq_millis = 2;
+
+    /* Packages that the pull is requested from */
+    repeated string packages = 3;
 }
 
 message ShellSubscription {
diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp
index bedfa7d..b877cc9 100755
--- a/cmds/statsd/src/socket/StatsSocketListener.cpp
+++ b/cmds/statsd/src/socket/StatsSocketListener.cpp
@@ -36,8 +36,6 @@
 namespace os {
 namespace statsd {
 
-static const int kLogMsgHeaderSize = 28;
-
 StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue)
     : SocketListener(getLogSocket(), false /*start listen*/), mQueue(queue) {
 }
@@ -92,7 +90,7 @@
         cred->uid = DEFAULT_OVERFLOWUID;
     }
 
-    char* ptr = ((char*)buffer) + sizeof(android_log_header_t);
+    uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t);
     n -= sizeof(android_log_header_t);
 
     // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would
@@ -121,18 +119,17 @@
         }
     }
 
-    log_msg msg;
-
-    msg.entry.len = n;
-    msg.entry.hdr_size = kLogMsgHeaderSize;
-    msg.entry.sec = time(nullptr);
-    msg.entry.pid = cred->pid;
-    msg.entry.uid = cred->uid;
-
-    memcpy(msg.buf + kLogMsgHeaderSize, ptr, n + 1);
+    // move past the 4-byte StatsEventTag
+    uint8_t* msg = ptr + sizeof(uint32_t);
+    uint32_t len = n - sizeof(uint32_t);
+    uint32_t uid = cred->uid;
+    uint32_t pid = cred->pid;
 
     int64_t oldestTimestamp;
-    if (!mQueue->push(std::make_unique<LogEvent>(msg), &oldestTimestamp)) {
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
+    logEvent->parseBuffer(msg, len);
+
+    if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) {
         StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp);
     }
 
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
new file mode 100644
index 0000000..6388001
--- /dev/null
+++ b/cmds/statsd/src/state/StateListener.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateListener : public virtual RefBase {
+public:
+    StateListener(){};
+
+    virtual ~StateListener(){};
+
+    /**
+     * Interface for handling a state change.
+     *
+     * The old and new state values map to the original state values.
+     * StateTrackers only track the original state values and are unaware
+     * of higher-level state groups. MetricProducers hold information on
+     * state groups and are responsible for mapping original state values to
+     * the correct state group.
+     *
+     * [eventTimeNs]: Time of the state change log event.
+     * [atomId]: The id of the state atom
+     * [primaryKey]: The primary field values of the state atom
+     * [oldState]: Previous state value before state change
+     * [newState]: Current state value after state change
+     */
+    virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                                const FieldValue& newState) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
new file mode 100644
index 0000000..c29afeb
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019, 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 false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateManager.h"
+
+#include <private/android_filesystem_config.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateManager::StateManager()
+    : mAllowedPkg({
+              "com.android.systemui",
+      }) {
+}
+
+StateManager& StateManager::getInstance() {
+    static StateManager sStateManager;
+    return sStateManager;
+}
+
+void StateManager::clear() {
+    mStateTrackers.clear();
+}
+
+void StateManager::onLogEvent(const LogEvent& event) {
+    // Only process state events from uids in AID_* and packages that are whitelisted in
+    // mAllowedPkg.
+    // Whitelisted AIDs are AID_ROOT and all AIDs in [1000, 2000)
+    if (event.GetUid() == AID_ROOT || (event.GetUid() >= 1000 && event.GetUid() < 2000) ||
+        mAllowedLogSources.find(event.GetUid()) != mAllowedLogSources.end()) {
+        if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
+            mStateTrackers[event.GetTagId()]->onLogEvent(event);
+        }
+    }
+}
+
+void StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) {
+    // Check if state tracker already exists.
+    if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
+        mStateTrackers[atomId] = new StateTracker(atomId);
+    }
+    mStateTrackers[atomId]->registerListener(listener);
+}
+
+void StateManager::unregisterListener(const int32_t atomId, wp<StateListener> listener) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // Hold the sp<> until the lock is released so that ~StateTracker() is
+    // not called while the lock is held.
+    sp<StateTracker> toRemove;
+
+    // Unregister listener from correct StateTracker
+    auto it = mStateTrackers.find(atomId);
+    if (it != mStateTrackers.end()) {
+        it->second->unregisterListener(listener);
+
+        // Remove the StateTracker if it has no listeners
+        if (it->second->getListenersCount() == 0) {
+            toRemove = it->second;
+            mStateTrackers.erase(it);
+        }
+    } else {
+        ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
+              atomId);
+    }
+    lock.unlock();
+}
+
+bool StateManager::getStateValue(const int32_t atomId, const HashableDimensionKey& key,
+                                 FieldValue* output) const {
+    auto it = mStateTrackers.find(atomId);
+    if (it != mStateTrackers.end()) {
+        return it->second->getStateValue(key, output);
+    }
+    return false;
+}
+
+void StateManager::updateLogSources(const sp<UidMap>& uidMap) {
+    mAllowedLogSources.clear();
+    for (const auto& pkg : mAllowedPkg) {
+        auto uids = uidMap->getAppUid(pkg);
+        mAllowedLogSources.insert(uids.begin(), uids.end());
+    }
+}
+
+void StateManager::notifyAppChanged(const string& apk, const sp<UidMap>& uidMap) {
+    if (mAllowedPkg.find(apk) != mAllowedPkg.end()) {
+        updateLogSources(uidMap);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
new file mode 100644
index 0000000..18c404c
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <inttypes.h>
+#include <utils/RefBase.h>
+
+#include <set>
+#include <string>
+#include <unordered_map>
+
+#include "HashableDimensionKey.h"
+#include "packages/UidMap.h"
+#include "state/StateListener.h"
+#include "state/StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * This class is NOT thread safe.
+ * It should only be used while StatsLogProcessor's lock is held.
+ */
+class StateManager : public virtual RefBase {
+public:
+    StateManager();
+
+    ~StateManager(){};
+
+    // Returns a pointer to the single, shared StateManager object.
+    static StateManager& getInstance();
+
+    // Unregisters all listeners and removes all trackers from StateManager.
+    void clear();
+
+    // Notifies the correct StateTracker of an event.
+    void onLogEvent(const LogEvent& event);
+
+    // Notifies the StateTracker for the given atomId to register listener.
+    // If the correct StateTracker does not exist, a new StateTracker is created.
+    // Note: StateTrackers can be created for non-state atoms. They are essentially empty and
+    // do not perform any actions.
+    void registerListener(const int32_t atomId, wp<StateListener> listener);
+
+    // Notifies the correct StateTracker to unregister a listener
+    // and removes the tracker if it no longer has any listeners.
+    void unregisterListener(const int32_t atomId, wp<StateListener> listener);
+
+    // Returns true if the StateTracker exists and queries for the
+    // original state value mapped to the given query key. The state value is
+    // stored and output in a FieldValue class.
+    // Returns false if the StateTracker doesn't exist.
+    bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                       FieldValue* output) const;
+
+    // Updates mAllowedLogSources with the latest uids for the packages that are allowed to log.
+    void updateLogSources(const sp<UidMap>& uidMap);
+
+    void notifyAppChanged(const string& apk, const sp<UidMap>& uidMap);
+
+    inline int getStateTrackersCount() const {
+        return mStateTrackers.size();
+    }
+
+    inline int getListenersCount(const int32_t atomId) const {
+        auto it = mStateTrackers.find(atomId);
+        if (it != mStateTrackers.end()) {
+            return it->second->getListenersCount();
+        }
+        return -1;
+    }
+
+private:
+    mutable std::mutex mMutex;
+
+    // Maps state atom ids to StateTrackers
+    std::unordered_map<int32_t, sp<StateTracker>> mStateTrackers;
+
+    // The package names that can log state events.
+    const std::set<std::string> mAllowedPkg;
+
+    // The combined uid sources (after translating pkg name to uid).
+    // State events from uids that are not in the list will be ignored to avoid state pollution.
+    std::set<int32_t> mAllowedLogSources;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
new file mode 100644
index 0000000..41e525c
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019, 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  // STOPSHIP if true
+#include "Log.h"
+
+#include "stats_util.h"
+
+#include "StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) {
+}
+
+void StateTracker::onLogEvent(const LogEvent& event) {
+    const int64_t eventTimeNs = event.GetElapsedTimestampNs();
+
+    // Parse event for primary field values i.e. primary key.
+    HashableDimensionKey primaryKey;
+    filterPrimaryKey(event.getValues(), &primaryKey);
+
+    FieldValue newState;
+    if (!getStateFieldValueFromLogEvent(event, &newState)) {
+        ALOGE("StateTracker error extracting state from log event. Missing exclusive state field.");
+        clearStateForPrimaryKey(eventTimeNs, primaryKey);
+        return;
+    }
+
+    mField.setField(newState.mField.getField());
+
+    if (newState.mValue.getType() != INT) {
+        ALOGE("StateTracker error extracting state from log event. Type: %d",
+              newState.mValue.getType());
+        clearStateForPrimaryKey(eventTimeNs, primaryKey);
+        return;
+    }
+
+    if (int resetState = event.getResetState(); resetState != -1) {
+        VLOG("StateTracker new reset state: %d", resetState);
+        const FieldValue resetStateFieldValue(mField, Value(resetState));
+        handleReset(eventTimeNs, resetStateFieldValue);
+        return;
+    }
+
+    const bool nested = newState.mAnnotations.isNested();
+    StateValueInfo* stateValueInfo = &mStateMap[primaryKey];
+    updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo);
+}
+
+void StateTracker::registerListener(wp<StateListener> listener) {
+    mListeners.insert(listener);
+}
+
+void StateTracker::unregisterListener(wp<StateListener> listener) {
+    mListeners.erase(listener);
+}
+
+bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const {
+    output->mField = mField;
+
+    if (const auto it = mStateMap.find(queryKey); it != mStateMap.end()) {
+        output->mValue = it->second.state;
+        return true;
+    }
+
+    // Set the state value to kStateUnknown if query key is not found in state map.
+    output->mValue = kStateUnknown;
+    return false;
+}
+
+void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) {
+    VLOG("StateTracker handle reset");
+    for (auto& [primaryKey, stateValueInfo] : mStateMap) {
+        updateStateForPrimaryKey(eventTimeNs, primaryKey, newState,
+                                 false /* nested; treat this state change as not nested */,
+                                 &stateValueInfo);
+    }
+}
+
+void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs,
+                                           const HashableDimensionKey& primaryKey) {
+    VLOG("StateTracker clear state for primary key");
+    const std::unordered_map<HashableDimensionKey, StateValueInfo>::iterator it =
+            mStateMap.find(primaryKey);
+
+    // If there is no entry for the primaryKey in mStateMap, then the state is already
+    // kStateUnknown.
+    const FieldValue state(mField, Value(kStateUnknown));
+    if (it != mStateMap.end()) {
+        updateStateForPrimaryKey(eventTimeNs, primaryKey, state,
+                                 false /* nested; treat this state change as not nested */,
+                                 &it->second);
+    }
+}
+
+void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs,
+                                            const HashableDimensionKey& primaryKey,
+                                            const FieldValue& newState, const bool nested,
+                                            StateValueInfo* stateValueInfo) {
+    FieldValue oldState;
+    oldState.mField = mField;
+    oldState.mValue.setInt(stateValueInfo->state);
+    const int32_t oldStateValue = stateValueInfo->state;
+    const int32_t newStateValue = newState.mValue.int_value;
+
+    if (kStateUnknown == newStateValue) {
+        mStateMap.erase(primaryKey);
+    }
+
+    // Update state map for non-nested counting case.
+    // Every state event triggers a state overwrite.
+    if (!nested) {
+        stateValueInfo->state = newStateValue;
+        stateValueInfo->count = 1;
+
+        // Notify listeners if state has changed.
+        if (oldStateValue != newStateValue) {
+            notifyListeners(eventTimeNs, primaryKey, oldState, newState);
+        }
+        return;
+    }
+
+    // Update state map for nested counting case.
+    //
+    // Nested counting is only allowed for binary state events such as ON/OFF or
+    // ACQUIRE/RELEASE. For example, WakelockStateChanged might have the state
+    // events: ON, ON, OFF. The state will still be ON until we see the same
+    // number of OFF events as ON events.
+    //
+    // In atoms.proto, a state atom with nested counting enabled
+    // must only have 2 states. There is no enforcemnt here of this requirement.
+    // The atom must be logged correctly.
+    if (kStateUnknown == newStateValue) {
+        if (kStateUnknown != oldStateValue) {
+            notifyListeners(eventTimeNs, primaryKey, oldState, newState);
+        }
+    } else if (oldStateValue == kStateUnknown) {
+        stateValueInfo->state = newStateValue;
+        stateValueInfo->count = 1;
+        notifyListeners(eventTimeNs, primaryKey, oldState, newState);
+    } else if (oldStateValue == newStateValue) {
+        stateValueInfo->count++;
+    } else if (--stateValueInfo->count == 0) {
+        stateValueInfo->state = newStateValue;
+        stateValueInfo->count = 1;
+        notifyListeners(eventTimeNs, primaryKey, oldState, newState);
+    }
+}
+
+void StateTracker::notifyListeners(const int64_t eventTimeNs,
+                                   const HashableDimensionKey& primaryKey,
+                                   const FieldValue& oldState, const FieldValue& newState) {
+    for (auto l : mListeners) {
+        auto sl = l.promote();
+        if (sl != nullptr) {
+            sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState);
+        }
+    }
+}
+
+bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) {
+    const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex();
+    if (-1 == exclusiveStateFieldIndex) {
+        ALOGE("error extracting state from log event. Missing exclusive state field.");
+        return false;
+    }
+
+    *output = event.getValues()[exclusiveStateFieldIndex];
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
new file mode 100644
index 0000000..abd579e
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+
+#include "state/StateListener.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateTracker : public virtual RefBase {
+public:
+    StateTracker(const int32_t atomId);
+
+    virtual ~StateTracker(){};
+
+    // Updates state map and notifies all listeners if a state change occurs.
+    // Checks if a state change has occurred by getting the state value from
+    // the log event and comparing the old and new states.
+    void onLogEvent(const LogEvent& event);
+
+    // Adds new listeners to set of StateListeners. If a listener is already
+    // registered, it is ignored.
+    void registerListener(wp<StateListener> listener);
+
+    void unregisterListener(wp<StateListener> listener);
+
+    // The output is a FieldValue object that has mStateField as the field and
+    // the original state value (found using the given query key) as the value.
+    //
+    // If the key isn't mapped to a state or the key size doesn't match the
+    // number of primary fields, the output value is set to kStateUnknown.
+    bool getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const;
+
+    inline int getListenersCount() const {
+        return mListeners.size();
+    }
+
+    const static int kStateUnknown = -1;
+
+private:
+    struct StateValueInfo {
+        int32_t state = kStateUnknown;  // state value
+        int count = 0;                  // nested count (only used for binary states)
+    };
+
+    Field mField;
+
+    // Maps primary key to state value info
+    std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
+
+    // Set of all StateListeners (objects listening for state changes)
+    std::set<wp<StateListener>> mListeners;
+
+    // Reset all state values in map to the given state.
+    void handleReset(const int64_t eventTimeNs, const FieldValue& newState);
+
+    // Clears the state value mapped to the given primary key by setting it to kStateUnknown.
+    void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey);
+
+    // Update the StateMap based on the received state value.
+    void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
+                                  const FieldValue& newState, const bool nested,
+                                  StateValueInfo* stateValueInfo);
+
+    // Notify registered state listeners of state change.
+    void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
+                         const FieldValue& oldState, const FieldValue& newState);
+};
+
+bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index b875470..ddd2725 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -41,6 +41,15 @@
   repeated DimensionsValue dimensions_value = 1;
 }
 
+message StateValue {
+  optional int32 atom_id = 1;
+
+  oneof contents {
+    int64 group_id = 2;
+    int32 value = 3;
+  }
+}
+
 message EventMetricData {
   optional int64 elapsed_timestamp_nanos = 1;
 
@@ -66,13 +75,15 @@
 message CountMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2;
+  repeated StateValue slice_by_state = 6;
 
   repeated CountBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
-  repeated DimensionsValue dimension_leaf_values_in_condition = 5;
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
+  repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
 message DurationBucketInfo {
@@ -92,13 +103,15 @@
 message DurationMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2;
+  repeated StateValue slice_by_state = 6;
 
   repeated DurationBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
-  repeated DimensionsValue dimension_leaf_values_in_condition = 5;
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
+  repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
 message ValueBucketInfo {
@@ -136,13 +149,15 @@
 message ValueMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2;
+  repeated StateValue slice_by_state = 6;
 
   repeated ValueBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
-  repeated DimensionsValue dimension_leaf_values_in_condition = 5;
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
+  repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
 message GaugeBucketInfo {
@@ -166,13 +181,16 @@
 message GaugeMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2;
+  // Currently unsupported
+  repeated StateValue slice_by_state = 6;
 
   repeated GaugeBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
-  repeated DimensionsValue dimension_leaf_values_in_condition = 5;
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
+  repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
 message StatsLogReport {
@@ -180,11 +198,42 @@
 
   // Fields 2 and 3 are reserved.
 
+  // Keep this in sync with BucketDropReason enum in MetricProducer.h.
+  enum BucketDropReason {
+      // For ValueMetric, a bucket is dropped during a dump report request iff
+      // current bucket should be included, a pull is needed (pulled metric and
+      // condition is true), and we are under fast time constraints.
+      DUMP_REPORT_REQUESTED = 1;
+      EVENT_IN_WRONG_BUCKET = 2;
+      CONDITION_UNKNOWN = 3;
+      PULL_FAILED = 4;
+      PULL_DELAYED = 5;
+      DIMENSION_GUARDRAIL_REACHED = 6;
+      MULTIPLE_BUCKETS_SKIPPED = 7;
+      // Not an invalid bucket case, but the bucket is dropped.
+      BUCKET_TOO_SMALL = 8;
+      // Not an invalid bucket case, but the bucket is skipped.
+      NO_DATA = 9;
+  };
+
+  message DropEvent {
+      optional BucketDropReason drop_reason = 1;
+
+      optional int64 drop_time_millis = 2;
+  }
+
   message SkippedBuckets {
       optional int64 start_bucket_elapsed_nanos = 1;
+
       optional int64 end_bucket_elapsed_nanos = 2;
+
       optional int64 start_bucket_elapsed_millis = 3;
+
       optional int64 end_bucket_elapsed_millis = 4;
+
+      // The number of drop events is capped by StatsdStats::kMaxLoggedBucketDropEvents.
+      // The current maximum is 10 drop events.
+      repeated DropEvent drop_event = 5;
   }
 
   message EventMetricDataWrapper {
@@ -220,7 +269,7 @@
 
   optional DimensionsValue dimensions_path_in_what = 11;
 
-  optional DimensionsValue dimensions_path_in_condition = 12;
+  optional DimensionsValue dimensions_path_in_condition = 12 [deprecated = true];
 
   // DO NOT USE field 13.
 
@@ -363,7 +412,7 @@
         repeated ConditionStats condition_stats = 14;
         repeated MetricStats metric_stats = 15;
         repeated AlertStats alert_stats = 16;
-        repeated MetricStats metric_dimension_in_condition_stats = 17;
+        repeated MetricStats metric_dimension_in_condition_stats = 17 [deprecated = true];
         message Annotation {
             optional int64 field_int64 = 1;
             optional int32 field_int32 = 2;
@@ -378,6 +427,7 @@
     message AtomStats {
         optional int32 tag = 1;
         optional int32 count = 2;
+        optional int32 error_count = 3;
     }
 
     repeated AtomStats atom_stats = 7;
@@ -408,11 +458,20 @@
         optional int64 pull_timeout = 10;
         optional int64 pull_exceed_max_delay = 11;
         optional int64 pull_failed = 12;
-        optional int64 stats_companion_pull_failed = 13;
-        optional int64 stats_companion_pull_binder_transaction_failed = 14;
+        optional int64 stats_companion_pull_failed = 13 [deprecated = true];
+        optional int64 stats_companion_pull_binder_transaction_failed = 14 [deprecated = true];
         optional int64 empty_data = 15;
         optional int64 registered_count = 16;
         optional int64 unregistered_count = 17;
+        optional int32 atom_error_count = 18;
+        optional int64 binder_call_failed = 19;
+        optional int64 failed_uid_provider_not_found = 20;
+        optional int64 puller_not_found = 21;
+        message PullTimeoutMetadata {
+          optional int64 pull_timeout_uptime_millis = 1;
+          optional int64 pull_timeout_elapsed_millis = 2;
+        }
+        repeated PullTimeoutMetadata pull_atom_metadata = 22;
     }
     repeated PulledAtomStats pulled_atom_stats = 10;
 
@@ -483,7 +542,7 @@
     message MetricValue {
         optional int64 metric_id = 1;
         optional DimensionsValue dimension_in_what = 2;
-        optional DimensionsValue dimension_in_condition = 3;
+        optional DimensionsValue dimension_in_condition = 3 [deprecated = true];
         optional int64 value = 4;
     }
     oneof value {
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index a7b6810..423bae8 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -17,11 +17,13 @@
 #include "hash.h"
 #include "stats_log_util.h"
 
+#include <aidl/android/os/IStatsCompanionService.h>
 #include <private/android_filesystem_config.h>
 #include <set>
 #include <utils/SystemClock.h>
 
-using android::util::AtomsInfo;
+#include "statscompanion_util.h"
+
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FIXED64;
@@ -33,6 +35,10 @@
 using android::util::FIELD_TYPE_UINT64;
 using android::util::ProtoOutputStream;
 
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+using std::string;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -49,6 +55,11 @@
 
 const int DIMENSIONS_VALUE_TUPLE_VALUE = 1;
 
+// for StateValue Proto
+const int STATE_VALUE_ATOM_ID = 1;
+const int STATE_VALUE_CONTENTS_GROUP_ID = 2;
+const int STATE_VALUE_CONTENTS_VALUE = 3;
+
 // for PulledAtomStats proto
 const int FIELD_ID_PULLED_ATOM_STATS = 10;
 const int FIELD_ID_PULL_ATOM_ID = 1;
@@ -63,11 +74,17 @@
 const int FIELD_ID_PULL_TIMEOUT = 10;
 const int FIELD_ID_PULL_EXCEED_MAX_DELAY = 11;
 const int FIELD_ID_PULL_FAILED = 12;
-const int FIELD_ID_STATS_COMPANION_FAILED = 13;
-const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14;
 const int FIELD_ID_EMPTY_DATA = 15;
 const int FIELD_ID_PULL_REGISTERED_COUNT = 16;
 const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17;
+const int FIELD_ID_ATOM_ERROR_COUNT = 18;
+const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19;
+const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20;
+const int FIELD_ID_PULLER_NOT_FOUND = 21;
+const int FIELD_ID_PULL_TIMEOUT_METADATA = 22;
+const int FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS = 1;
+const int FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS = 2;
+
 // for AtomMetricStats proto
 const int FIELD_ID_ATOM_METRIC_STATS = 17;
 const int FIELD_ID_METRIC_ID = 1;
@@ -345,30 +362,7 @@
                     protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value);
                     break;
                 case STRING: {
-                    bool isBytesField = false;
-                    // Bytes field is logged via string format in log_msg format. So here we check
-                    // if this string field is a byte field.
-                    std::map<int, std::vector<int>>::const_iterator itr;
-                    if (depth == 0 && (itr = AtomsInfo::kBytesFieldAtoms.find(tagId)) !=
-                                              AtomsInfo::kBytesFieldAtoms.end()) {
-                        const std::vector<int>& bytesFields = itr->second;
-                        for (int bytesField : bytesFields) {
-                            if (bytesField == fieldNum) {
-                                // This is a bytes field
-                                isBytesField = true;
-                                break;
-                            }
-                        }
-                    }
-                    if (isBytesField) {
-                        if (dim.mValue.str_value.length() > 0) {
-                            protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
-                                               (const char*)dim.mValue.str_value.c_str(),
-                                               dim.mValue.str_value.length());
-                        }
-                    } else {
-                        protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
-                    }
+                    protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
                     break;
                 }
                 case STORAGE:
@@ -412,6 +406,23 @@
     protoOutput->end(atomToken);
 }
 
+void writeStateToProto(const FieldValue& state, util::ProtoOutputStream* protoOutput) {
+    protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_ATOM_ID, state.mField.getTag());
+
+    switch (state.mValue.getType()) {
+        case INT:
+            protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_CONTENTS_VALUE,
+                               state.mValue.int_value);
+            break;
+        case LONG:
+            protoOutput->write(FIELD_TYPE_INT64 | STATE_VALUE_CONTENTS_GROUP_ID,
+                               state.mValue.long_value);
+            break;
+        default:
+            break;
+    }
+}
+
 int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) {
     int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit);
     if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL &&
@@ -441,6 +452,8 @@
             return 12 * 60 * 60 * 1000LL;
         case ONE_DAY:
             return 24 * 60 * 60 * 1000LL;
+        case ONE_WEEK:
+            return 7 * 24 * 60 * 60 * 1000LL;
         case CTS:
             return 1000;
         case TIME_UNIT_UNSPECIFIED:
@@ -474,16 +487,29 @@
                        (long long)pair.second.pullExceedMaxDelay);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED,
                        (long long)pair.second.pullFailed);
-    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_FAILED,
-                       (long long)pair.second.statsCompanionPullFailed);
-    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED,
-                       (long long)pair.second.statsCompanionPullBinderTransactionFailed);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA,
                        (long long)pair.second.emptyData);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT,
                        (long long) pair.second.registeredCount);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT,
                        (long long) pair.second.unregisteredCount);
+    protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT,
+                       (long long)pair.second.binderCallFailCount);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND,
+                       (long long)pair.second.pullUidProviderNotFound);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND,
+                       (long long)pair.second.pullerNotFound);
+    for (const auto& pullTimeoutMetadata : pair.second.pullTimeoutMetadata) {
+        uint64_t timeoutMetadataToken = protoOutput->start(FIELD_TYPE_MESSAGE |
+                                                           FIELD_ID_PULL_TIMEOUT_METADATA |
+                                                           FIELD_COUNT_REPEATED);
+        protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS,
+                           pullTimeoutMetadata.pullTimeoutUptimeMillis);
+        protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS,
+                           pullTimeoutMetadata.pullTimeoutElapsedMillis);
+        protoOutput->end(timeoutMetadataToken);
+    }
     protoOutput->end(token);
 }
 
@@ -529,6 +555,10 @@
     return ::android::elapsedRealtime();
 }
 
+int64_t getSystemUptimeMillis() {
+    return ::android::uptimeMillis();
+}
+
 int64_t getWallClockNs() {
     return time(nullptr) * NS_PER_SEC;
 }
@@ -541,14 +571,13 @@
     return time(nullptr) * MS_PER_SEC;
 }
 
-int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs) {
-    if (AtomsInfo::kTruncatingTimestampAtomBlackList.find(atomId) !=
-            AtomsInfo::kTruncatingTimestampAtomBlackList.end() ||
-        (atomId >= StatsdStats::kTimestampTruncationStartTag &&
-         atomId <= StatsdStats::kTimestampTruncationEndTag)) {
-        return timestampNs / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60);
+int64_t truncateTimestampIfNecessary(const LogEvent& event) {
+    if (event.shouldTruncateTimestamp() ||
+        (event.GetTagId() >= StatsdStats::kTimestampTruncationStartTag &&
+         event.GetTagId() <= StatsdStats::kTimestampTruncationEndTag)) {
+        return event.GetElapsedTimestampNs() / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60);
     } else {
-        return timestampNs;
+        return event.GetElapsedTimestampNs();
     }
 }
 
@@ -560,6 +589,21 @@
     return millis * 1000000;
 }
 
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) {
+    shared_ptr<IStatsCompanionService> scs = getStatsCompanionService();
+    if (scs == nullptr) {
+        return false;
+    }
+
+    bool success;
+    ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success);
+    if (!status.isOk()) {
+        return false;
+    }
+
+    return success;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index a28165d..eb65dc6 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -17,28 +17,33 @@
 #pragma once
 
 #include <android/util/ProtoOutputStream.h>
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
-#include "atoms_info.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
+
+using android::util::ProtoOutputStream;
 
 namespace android {
 namespace os {
 namespace statsd {
 
 void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& values,
-                                 util::ProtoOutputStream* protoOutput);
+                                 ProtoOutputStream* protoOutput);
 void writeDimensionToProto(const HashableDimensionKey& dimension, std::set<string> *str_set,
-                           util::ProtoOutputStream* protoOutput);
+                           ProtoOutputStream* protoOutput);
 
 void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension,
                                     const int dimensionLeafFieldId,
                                     std::set<string> *str_set,
-                                    util::ProtoOutputStream* protoOutput);
+                                    ProtoOutputStream* protoOutput);
 
 void writeDimensionPathToProto(const std::vector<Matcher>& fieldMatchers,
-                               util::ProtoOutputStream* protoOutput);
+                               ProtoOutputStream* protoOutput);
+
+void writeStateToProto(const FieldValue& state, ProtoOutputStream* protoOutput);
 
 // Convert the TimeUnit enum to the bucket size in millis with a guardrail on
 // bucket size.
@@ -56,6 +61,9 @@
 // Gets the elapsed timestamp in seconds.
 int64_t getElapsedRealtimeSec();
 
+// Gets the system uptime in millis.
+int64_t getSystemUptimeMillis();
+
 // Gets the wall clock timestamp in ns.
 int64_t getWallClockNs();
 
@@ -71,14 +79,14 @@
 
 // Helper function to write PulledAtomStats to ProtoOutputStream
 void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
-                              util::ProtoOutputStream* protoOutput);
+                              ProtoOutputStream* protoOutput);
 
 // Helper function to write AtomMetricStats to ProtoOutputStream
 void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetricStats> &pair,
-                                  util::ProtoOutputStream *protoOutput);
+                                  ProtoOutputStream *protoOutput);
 
 template<class T>
-bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) {
+bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) {
     std::string pbBytes;
     sp<android::util::ProtoReader> reader = protoOutput.data();
     while (reader->readBuffer() != NULL) {
@@ -89,14 +97,21 @@
     return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
 }
 
-// Checks the blacklist of atoms as well as the blacklisted range of 300,000 - 304,999.
+// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999.
 // Returns the truncated timestamp to the nearest 5 minutes if needed.
-int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs);
+int64_t truncateTimestampIfNecessary(const LogEvent& event);
+
+// Checks permission for given pid and uid.
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid);
 
 inline bool isVendorPulledAtom(int atomId) {
     return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag;
 }
 
+inline bool isPulledAtom(int atomId) {
+    return atomId >= StatsdStats::kPullAtomStartTag && atomId < StatsdStats::kVendorAtomStartTag;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statscompanion_util.cpp b/cmds/statsd/src/statscompanion_util.cpp
index d338827..ce07ec0 100644
--- a/cmds/statsd/src/statscompanion_util.cpp
+++ b/cmds/statsd/src/statscompanion_util.cpp
@@ -18,26 +18,16 @@
 #include "Log.h"
 
 #include "statscompanion_util.h"
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
 
 namespace android {
 namespace os {
 namespace statsd {
 
-sp <IStatsCompanionService> getStatsCompanionService() {
-    sp<IStatsCompanionService> statsCompanion = nullptr;
-    // Get statscompanion service from service manager
-    static const sp <IServiceManager> sm(defaultServiceManager());
-    if (statsCompanion == nullptr) {
-        if (sm != nullptr) {
-            const String16 name("statscompanion");
-            statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
-            if (statsCompanion == nullptr) {
-                ALOGW("statscompanion service unavailable!");
-                return nullptr;
-            }
-        }
-    }
-    return statsCompanion;
+shared_ptr<IStatsCompanionService> getStatsCompanionService() {
+    ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion"));
+    return IStatsCompanionService::fromBinder(binder);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/statscompanion_util.h b/cmds/statsd/src/statscompanion_util.h
index dc4f283..e20c40b 100644
--- a/cmds/statsd/src/statscompanion_util.h
+++ b/cmds/statsd/src/statscompanion_util.h
@@ -16,14 +16,17 @@
 
 #pragma once
 
-#include "StatsLogProcessor.h"
+#include <aidl/android/os/IStatsCompanionService.h>
+
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
 namespace statsd {
 
 /** Fetches and returns the StatsCompanionService. */
-sp<IStatsCompanionService> getStatsCompanionService();
+shared_ptr<IStatsCompanionService> getStatsCompanionService();
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 79c06b9..acdffd3 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -35,7 +35,7 @@
 
 enum TimeUnit {
   TIME_UNIT_UNSPECIFIED = 0;
-  ONE_MINUTE = 1;
+  ONE_MINUTE = 1;  // WILL BE GUARDRAILED TO 5 MINS UNLESS UID = SHELL OR ROOT
   FIVE_MINUTES = 2;
   TEN_MINUTES = 3;
   THIRTY_MINUTES = 4;
@@ -44,6 +44,7 @@
   SIX_HOURS = 7;
   TWELVE_HOURS = 8;
   ONE_DAY = 9;
+  ONE_WEEK = 10;
   CTS = 1000;
 }
 
@@ -130,7 +131,7 @@
     UNKNOWN = 0;
     FALSE = 1;
   }
-  optional InitialValue initial_value = 5 [default = FALSE];
+  optional InitialValue initial_value = 5 [default = UNKNOWN];
 
   optional FieldMatcher dimensions = 6;
 }
@@ -150,6 +151,24 @@
   }
 }
 
+message StateMap {
+  message StateGroup {
+    optional int64 group_id = 1;
+
+    repeated int32 value = 2;
+  }
+
+  repeated StateGroup group = 1;
+}
+
+message State {
+  optional int64 id = 1;
+
+  optional int32 atom_id = 2;
+
+  optional StateMap map = 3;
+}
+
 message MetricConditionLink {
   optional int64 condition = 1;
 
@@ -158,6 +177,14 @@
   optional FieldMatcher fields_in_condition = 3;
 }
 
+message MetricStateLink {
+  optional int32 state_atom_id = 1;
+
+  optional FieldMatcher fields_in_what = 2;
+
+  optional FieldMatcher fields_in_state = 3;
+}
+
 message FieldFilter {
   optional bool include_all = 1 [default = false];
   optional FieldMatcher fields = 2;
@@ -171,6 +198,9 @@
   optional int64 condition = 3;
 
   repeated MetricConditionLink links = 4;
+
+  reserved 100;
+  reserved 101;
 }
 
 message CountMetric {
@@ -182,11 +212,18 @@
 
   optional FieldMatcher dimensions_in_what = 4;
 
-  optional FieldMatcher dimensions_in_condition = 7;
+  repeated int64 slice_by_state = 8;
 
   optional TimeUnit bucket = 5;
 
   repeated MetricConditionLink links = 6;
+
+  repeated MetricStateLink state_link = 9;
+
+  optional FieldMatcher dimensions_in_condition = 7 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message DurationMetric {
@@ -196,8 +233,12 @@
 
   optional int64 condition = 3;
 
+  repeated int64 slice_by_state = 9;
+
   repeated MetricConditionLink links = 4;
 
+  repeated MetricStateLink state_link = 10;
+
   enum AggregationType {
     SUM = 1;
 
@@ -207,9 +248,12 @@
 
   optional FieldMatcher dimensions_in_what = 6;
 
-  optional FieldMatcher dimensions_in_condition = 8;
-
   optional TimeUnit bucket = 7;
+
+  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message GaugeMetric {
@@ -225,7 +269,7 @@
 
   optional FieldMatcher dimensions_in_what = 5;
 
-  optional FieldMatcher dimensions_in_condition = 8;
+  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
 
   optional TimeUnit bucket = 6;
 
@@ -243,9 +287,12 @@
 
   optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10];
 
-  optional int32 max_pull_delay_sec = 13 [default = 10];
+  optional int32 max_pull_delay_sec = 13 [default = 30];
 
   optional bool split_bucket_for_app_upgrade = 14 [default = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message ValueMetric {
@@ -259,12 +306,14 @@
 
   optional FieldMatcher dimensions_in_what = 5;
 
-  optional FieldMatcher dimensions_in_condition = 9;
+  repeated int64 slice_by_state = 18;
 
   optional TimeUnit bucket = 6;
 
   repeated MetricConditionLink links = 7;
 
+  repeated MetricStateLink state_link = 19;
+
   enum AggregationType {
     SUM = 1;
     MIN = 2;
@@ -291,9 +340,14 @@
 
   optional bool skip_zero_diff_output = 14 [default = true];
 
-  optional int32 max_pull_delay_sec = 16 [default = 10];
+  optional int32 max_pull_delay_sec = 16 [default = 30];
 
   optional bool split_bucket_for_app_upgrade = 17 [default = true];
+
+  optional FieldMatcher dimensions_in_condition = 9 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message Alert {
@@ -393,6 +447,12 @@
   repeated EventActivation event_activation = 2;
 }
 
+message PullAtomPackages {
+    optional int32 atom_id = 1;
+
+    repeated string packages = 2;
+}
+
 message StatsdConfig {
   optional int64 id = 1;
 
@@ -438,6 +498,14 @@
 
   optional bool persist_locally = 20 [default = false];
 
+  repeated State state = 21;
+
+  repeated string default_pull_packages = 22;
+
+  repeated PullAtomPackages pull_atom_packages = 23;
+
+  repeated int32 whitelisted_atom_ids = 24;
+
   // Field number 1000 is reserved for later use.
   reserved 1000;
 }
diff --git a/cmds/statsd/src/statsd_metadata.proto b/cmds/statsd/src/statsd_metadata.proto
new file mode 100644
index 0000000..200b392
--- /dev/null
+++ b/cmds/statsd/src/statsd_metadata.proto
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd.metadata;
+
+message ConfigKey {
+  optional int64 config_id = 1;
+  optional int32 uid = 2;
+}
+
+message Field {
+  optional int32 tag = 1;
+  optional int32 field = 2;
+}
+
+message FieldValue {
+  optional Field field = 1;
+  oneof value {
+    int32 value_int = 2;
+    int64 value_long = 3;
+    float value_float = 4;
+    double value_double = 5;
+    string value_str = 6;
+    bytes value_storage = 7;
+  }
+}
+
+message MetricDimensionKey {
+  repeated FieldValue dimension_key_in_what = 1;
+  repeated FieldValue state_values_key = 2;
+}
+
+message AlertDimensionKeyedData {
+  // The earliest time the alert can be fired again in wall clock time.
+  optional int32 last_refractory_ends_sec = 1;
+  optional MetricDimensionKey dimension_key = 2;
+}
+
+message AlertMetadata {
+  optional int64 alert_id = 1;
+  repeated AlertDimensionKeyedData alert_dim_keyed_data = 2;
+}
+
+// All metadata for a config in statsd
+message StatsMetadata {
+  optional ConfigKey config_key = 1;
+  repeated AlertMetadata alert_metadata = 2;
+}
+
+message StatsMetadataList {
+  repeated StatsMetadata stats_metadata = 1;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 507297c..dcfdfe3 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -44,7 +44,7 @@
 #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin"
 
 // Magic word at the start of the train info file, change this if changing the file format
-const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff;
+const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf;
 
 // for ConfigMetricsReportList
 const int FIELD_ID_REPORTS = 2;
@@ -75,6 +75,29 @@
                         (long long)id);
 }
 
+static string findTrainInfoFileNameLocked(const string& trainName) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
+    if (dir == NULL) {
+        VLOG("Path %s does not exist", TRAIN_INFO_DIR);
+        return "";
+    }
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* fileName = de->d_name;
+        if (fileName[0] == '.') continue;
+
+        size_t fileNameLength = strlen(fileName);
+        if (fileNameLength >= trainName.length()) {
+            if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(),
+                             trainName.length())) {
+              return string(fileName);
+            }
+        }
+    }
+
+    return "";
+}
+
 // Returns array of int64_t which contains timestamp in seconds, uid,
 // configID and whether the file is a local history file.
 static void parseFileName(char* name, FileName* output) {
@@ -123,20 +146,25 @@
     close(fd);
 }
 
-bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                                    int32_t status, const std::vector<int64_t>& experimentIds) {
+bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) {
     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
 
-    deleteAllFiles(TRAIN_INFO_DIR);
+    if (trainInfo.trainName.empty()) {
+      return false;
+    }
+    deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str());
 
-    int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
+    std::string fileName =
+            StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(),
+                         trainInfo.trainName.c_str());
+
+    int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
     if (fd == -1) {
-        VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH);
+        VLOG("Attempt to access %s but failed", fileName.c_str());
         return false;
     }
 
     size_t result;
-
     // Write the magic word
     result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC));
     if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) {
@@ -146,8 +174,8 @@
     }
 
     // Write the train version
-    const size_t trainVersionCodeByteCount = sizeof(trainVersionCode);
-    result = write(fd, &trainVersionCode, trainVersionCodeByteCount);
+    const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode);
+    result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
     if (result != trainVersionCodeByteCount) {
         VLOG("Failed to wrtie train version code");
         close(fd);
@@ -155,7 +183,7 @@
     }
 
     // Write # of bytes in trainName to file
-    const size_t trainNameSize = trainName.size();
+    const size_t trainNameSize = trainInfo.trainName.size();
     const size_t trainNameSizeByteCount = sizeof(trainNameSize);
     result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount);
     if (result != trainNameSizeByteCount) {
@@ -165,7 +193,7 @@
     }
 
     // Write trainName to file
-    result = write(fd, trainName.c_str(), trainNameSize);
+    result = write(fd, trainInfo.trainName.c_str(), trainNameSize);
     if (result != trainNameSize) {
         VLOG("Failed to write train name");
         close(fd);
@@ -173,8 +201,8 @@
     }
 
     // Write status to file
-    const size_t statusByteCount = sizeof(status);
-    result = write(fd, (uint8_t*)&status, statusByteCount);
+    const size_t statusByteCount = sizeof(trainInfo.status);
+    result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount);
     if (result != statusByteCount) {
         VLOG("Failed to write status");
         close(fd);
@@ -182,7 +210,7 @@
     }
 
     // Write experiment id count to file.
-    const size_t experimentIdsCount = experimentIds.size();
+    const size_t experimentIdsCount = trainInfo.experimentIds.size();
     const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount);
     result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount);
     if (result != experimentIdsCountByteCount) {
@@ -193,7 +221,7 @@
 
     // Write experimentIds to file
     for (size_t i = 0; i < experimentIdsCount; i++) {
-        const int64_t experimentId = experimentIds[i];
+        const int64_t experimentId = trainInfo.experimentIds[i];
         const size_t experimentIdByteCount = sizeof(experimentId);
         result = write(fd, &experimentId, experimentIdByteCount);
         if (result == experimentIdByteCount) {
@@ -205,23 +233,47 @@
         }
     }
 
-    result = fchown(fd, AID_STATSD, AID_STATSD);
-    if (result) {
-        VLOG("Failed to chown train info file to statsd");
-        close(fd);
-        return false;
+    // Write bools to file
+    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
+    result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write requires staging");
+      close(fd);
+      return false;
+    }
+
+    result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write rollback enabled");
+      close(fd);
+      return false;
+    }
+
+    result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write requires log latency monitor");
+      close(fd);
+      return false;
     }
 
     close(fd);
     return true;
 }
 
-bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) {
+bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) {
     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
+    return readTrainInfoLocked(trainName, trainInfo);
+}
 
-    int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC);
+bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) {
+    trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true);
+    string fileName = findTrainInfoFileNameLocked(trainName);
+    if (fileName.empty()) {
+        return false;
+    }
+    int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
-        VLOG("Failed to open train-info.bin");
+        VLOG("Failed to open %s", fileName.c_str());
         return false;
     }
 
@@ -297,6 +349,29 @@
         trainInfo.experimentIds.push_back(experimentId);
     }
 
+    // Read bools
+    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
+    result = read(fd, &trainInfo.requiresStaging, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires requires staging from train info file");
+        close(fd);
+        return false;
+    }
+
+    result = read(fd, &trainInfo.rollbackEnabled, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires rollback enabled from train info file");
+        close(fd);
+        return false;
+    }
+
+    result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires requires low latency monitor from train info file");
+        close(fd);
+        return false;
+    }
+
     // Expect to be at EOF.
     char c;
     result = read(fd, &c, 1);
@@ -311,6 +386,32 @@
     return true;
 }
 
+vector<InstallTrainInfo> StorageManager::readAllTrainInfo() {
+    std::lock_guard<std::mutex> lock(sTrainInfoMutex);
+    vector<InstallTrainInfo> trainInfoList;
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
+    if (dir == NULL) {
+        VLOG("Directory does not exist: %s", TRAIN_INFO_DIR);
+        return trainInfoList;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') {
+            continue;
+        }
+
+        InstallTrainInfo trainInfo;
+        bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo);
+        if (!readSuccess) {
+            continue;
+        }
+        trainInfoList.push_back(trainInfo);
+    }
+    return trainInfoList;
+}
+
 void StorageManager::deleteFile(const char* file) {
     if (remove(file) != 0) {
         VLOG("Attempt to delete %s but is not found", file);
@@ -574,7 +675,7 @@
     });
 }
 
-void StorageManager::trimToFit(const char* path) {
+void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) {
     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
     if (dir == NULL) {
         VLOG("Path %s does not exist", path);
@@ -589,9 +690,16 @@
         if (name[0] == '.') continue;
 
         FileName output;
-        parseFileName(name, &output);
+        string file_name;
+        if (parseTimestampOnly) {
+            file_name = StringPrintf("%s/%s", path, name);
+            output.mTimestampSec = StrToInt64(strtok(name, "_"));
+            output.mIsHistory = false;
+        } else {
+            parseFileName(name, &output);
+            file_name = output.getFullFileName(path);
+        }
         if (output.mTimestampSec == -1) continue;
-        string file_name = output.getFullFileName(path);
 
         // Check for timestamp and delete if it's too old.
         long fileAge = nowSec - output.mTimestampSec;
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index 69b41c2..d59046d 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -52,13 +52,22 @@
     /**
      * Writes train info.
      */
-    static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                               int32_t status, const std::vector<int64_t>& experimentIds);
+    static bool writeTrainInfo(const InstallTrainInfo& trainInfo);
 
     /**
      * Reads train info.
      */
-    static bool readTrainInfo(InstallTrainInfo& trainInfo);
+    static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo);
+
+    /**
+     * Reads train info assuming lock is obtained.
+     */
+    static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo);
+
+    /**
+     * Reads all train info and returns a vector of train info.
+     */
+    static vector<InstallTrainInfo> readAllTrainInfo();
 
     /**
      * Reads the file content to the buffer.
@@ -124,7 +133,7 @@
      * Trims files in the provided directory to limit the total size, number of
      * files, accumulation of outdated files.
      */
-    static void trimToFit(const char* dir);
+    static void trimToFit(const char* dir, bool parseTimestampOnly = false);
 
     /**
      * Returns true if there already exists identical configuration on device.
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index ba5e667..1d77513 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -21,10 +21,8 @@
 #include "packages/UidMap.h"
 #include "stats_log_util.h"
 
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
 #include <android/util/ProtoOutputStream.h>
-#include <binder/IServiceManager.h>
+#include <incident/incident_report.h>
 
 #include <vector>
 
@@ -51,7 +49,6 @@
 const int FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC = 1;
 const int FIELD_ID_METRIC_VALUE_METRIC_ID = 1;
 const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT = 2;
-const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION = 3;
 const int FIELD_ID_METRIC_VALUE_VALUE = 4;
 
 const int FIELD_ID_PACKAGE_INFO = 3;
@@ -83,10 +80,8 @@
     writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), nullptr, &headerProto);
     headerProto.end(dimToken);
 
+    // deprecated field
     // optional DimensionsValue dimension_in_condition = 3;
-    dimToken = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION);
-    writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), nullptr, &headerProto);
-    headerProto.end(dimToken);
 
     // optional int64 value = 4;
     headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_VALUE, (long long)metricValue);
@@ -105,13 +100,6 @@
         }
     }
 
-    for (const auto& dim : dimensionKey.getDimensionKeyInCondition().getValues()) {
-        int uid = getUidIfExists(dim);
-        if (uid > 2000) {
-            uids.insert(uid);
-        }
-    }
-
     if (!uids.empty()) {
         uint64_t token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PACKAGE_INFO);
         UidMap::getInstance()->writeUidMapSnapshot(getElapsedRealtimeNs(), true, true, uids,
@@ -142,44 +130,38 @@
         return false;
     }
 
-    IncidentReportArgs incidentReport;
+    AIncidentReportArgs* args = AIncidentReportArgs_init();
 
     vector<uint8_t> protoData;
     getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey,
                  config.alert_description(), &protoData);
-    incidentReport.addHeader(protoData);
+    AIncidentReportArgs_addHeader(args, protoData.data(), protoData.size());
 
     for (int i = 0; i < config.section_size(); i++) {
-        incidentReport.addSection(config.section(i));
+        AIncidentReportArgs_addSection(args, config.section(i));
     }
 
     uint8_t dest;
     switch (config.dest()) {
         case IncidentdDetails_Destination_AUTOMATIC:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
             break;
         case IncidentdDetails_Destination_EXPLICIT:
-            dest = android::os::PRIVACY_POLICY_EXPLICIT;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT;
             break;
         default:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
     }
-    incidentReport.setPrivacyPolicy(dest);
+    AIncidentReportArgs_setPrivacyPolicy(args, dest);
 
-    incidentReport.setReceiverPkg(config.receiver_pkg());
+    AIncidentReportArgs_setReceiverPackage(args, config.receiver_pkg().c_str());
 
-    incidentReport.setReceiverCls(config.receiver_cls());
+    AIncidentReportArgs_setReceiverClass(args, config.receiver_cls().c_str());
 
-    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
-            defaultServiceManager()->getService(android::String16("incident")));
-    if (service == nullptr) {
-        ALOGW("Failed to fetch incident service.");
-        return false;
-    }
-    VLOG("Calling incidentd %p", service.get());
-    binder::Status s = service->reportIncident(incidentReport);
-    VLOG("Report incident status: %s", s.toString8().string());
-    return s.isOk();
+    int err = AIncidentReportArgs_takeReport(args);
+    AIncidentReportArgs_delete(args);
+
+    return err == NO_ERROR;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index d4f4478..c915ef3 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -19,7 +19,6 @@
 
 #include "SubscriberReporter.h"
 
-using android::IBinder;
 using std::lock_guard;
 
 namespace android {
@@ -28,18 +27,66 @@
 
 using std::vector;
 
+struct BroadcastSubscriberDeathCookie {
+    BroadcastSubscriberDeathCookie(const ConfigKey& configKey, int64_t subscriberId,
+                                   const shared_ptr<IPendingIntentRef>& pir):
+        mConfigKey(configKey),
+        mSubscriberId(subscriberId),
+        mPir(pir) {}
+
+    ConfigKey mConfigKey;
+    int64_t mSubscriberId;
+    shared_ptr<IPendingIntentRef> mPir;
+};
+
+void SubscriberReporter::broadcastSubscriberDied(void* cookie) {
+    auto cookie_ = static_cast<BroadcastSubscriberDeathCookie*>(cookie);
+    ConfigKey& configKey = cookie_->mConfigKey;
+    int64_t subscriberId = cookie_->mSubscriberId;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
+
+    SubscriberReporter& thiz = getInstance();
+
+    // Erase the mapping from a (config_key, subscriberId) to a pir if the
+    // mapping exists.
+    lock_guard<mutex> lock(thiz.mLock);
+    auto subscriberMapIt = thiz.mIntentMap.find(configKey);
+    if (subscriberMapIt != thiz.mIntentMap.end()) {
+        auto subscriberMap = subscriberMapIt->second;
+        auto pirIt = subscriberMap.find(subscriberId);
+        if (pirIt != subscriberMap.end() && pirIt->second == pir) {
+            subscriberMap.erase(subscriberId);
+            if (subscriberMap.empty()) {
+                thiz.mIntentMap.erase(configKey);
+            }
+        }
+    }
+
+    // The death recipient corresponding to this specific pir can never be
+    // triggered again, so free up resources.
+    delete cookie_;
+}
+
+SubscriberReporter::SubscriberReporter() :
+    mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) {
+}
+
 void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
                                                 int64_t subscriberId,
-                                                const sp<IBinder>& intentSender) {
+                                                const shared_ptr<IPendingIntentRef>& pir) {
     VLOG("SubscriberReporter::setBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
-    mIntentMap[configKey][subscriberId] = intentSender;
+    {
+        lock_guard<mutex> lock(mLock);
+        mIntentMap[configKey][subscriberId] = pir;
+    }
+    AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(),
+                         new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir));
 }
 
 void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
                                                   int64_t subscriberId) {
     VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
+    lock_guard<mutex> lock(mLock);
     auto subscriberMapIt = mIntentMap.find(configKey);
     if (subscriberMapIt != mIntentMap.end()) {
         subscriberMapIt->second.erase(subscriberId);
@@ -49,12 +96,6 @@
     }
 }
 
-void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
-    VLOG("SubscriberReporter::removeConfig called.");
-    lock_guard<std::mutex> lock(mLock);
-    mIntentMap.erase(configKey);
-}
-
 void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
                                                   const Subscription& subscription,
                                                   const MetricDimensionKey& dimKey) const {
@@ -67,7 +108,7 @@
     //  config id - the name of this config (for this particular uid)
 
     VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
-    lock_guard<std::mutex> lock(mLock);
+    lock_guard<mutex> lock(mLock);
 
     if (!subscription.has_broadcast_subscriber_details()
             || !subscription.broadcast_subscriber_details().has_subscriber_id()) {
@@ -76,10 +117,10 @@
     }
     int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
 
-    vector<String16> cookies;
+    vector<string> cookies;
     cookies.reserve(subscription.broadcast_subscriber_details().cookie_size());
     for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) {
-        cookies.push_back(String16(cookie.c_str()));
+        cookies.push_back(cookie);
     }
 
     auto it1 = mIntentMap.find(configKey);
@@ -96,79 +137,33 @@
     sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey);
 }
 
-void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
+void SubscriberReporter::sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
                                              const ConfigKey& configKey,
                                              const Subscription& subscription,
-                                             const vector<String16>& cookies,
+                                             const vector<string>& cookies,
                                              const MetricDimensionKey& dimKey) const {
     VLOG("SubscriberReporter::sendBroadcastLocked called.");
-    if (mStatsCompanionService == nullptr) {
-        ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
-        return;
-    }
-    mStatsCompanionService->sendSubscriberBroadcast(
-            intentSender,
+    pir->sendSubscriberBroadcast(
             configKey.GetUid(),
             configKey.GetId(),
             subscription.id(),
             subscription.rule_id(),
             cookies,
-            getStatsDimensionsValue(dimKey.getDimensionKeyInWhat()));
+            dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel());
 }
 
-void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth,
-                                   int prefix, vector<StatsDimensionsValue>* output) {
-    size_t count = dims.size();
-    while (*index < count) {
-        const auto& dim = dims[*index];
-        const int valueDepth = dim.mField.getDepth();
-        const int valuePrefix = dim.mField.getPrefix(depth);
-        if (valueDepth > 2) {
-            ALOGE("Depth > 2 not supported");
-            return;
-        }
-        if (depth == valueDepth && valuePrefix == prefix) {
-            switch (dim.mValue.getType()) {
-                case INT:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.int_value));
-                    break;
-                case LONG:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.long_value));
-                    break;
-                case FLOAT:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.float_value));
-                    break;
-                case STRING:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           String16(dim.mValue.str_value.c_str())));
-                    break;
-                default:
-                    break;
-            }
-            (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
-            vector<StatsDimensionsValue> childOutput;
-            getStatsDimensionsValueHelper(dims, index, depth + 1, dim.mField.getPrefix(depth + 1),
-                                          &childOutput);
-            output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), childOutput));
-        } else {
-            return;
-        }
+shared_ptr<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
+                                                                         int64_t subscriberId) {
+    lock_guard<mutex> lock(mLock);
+    auto subscriberMapIt = mIntentMap.find(configKey);
+    if (subscriberMapIt == mIntentMap.end()) {
+        return nullptr;
     }
-}
-
-StatsDimensionsValue SubscriberReporter::getStatsDimensionsValue(const HashableDimensionKey& dim) {
-    if (dim.getValues().size() == 0) {
-        return StatsDimensionsValue();
+    auto pirMapIt = subscriberMapIt->second.find(subscriberId);
+    if (pirMapIt == subscriberMapIt->second.end()) {
+        return nullptr;
     }
-
-    vector<StatsDimensionsValue> fields;
-    size_t index = 0;
-    getStatsDimensionsValueHelper(dim.getValues(), &index, 0, 0, &fields);
-    return StatsDimensionsValue(dim.getValues()[0].mField.getTag(), fields);
+    return pirMapIt->second;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 2a7f771..4fe4281 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -16,18 +16,25 @@
 
 #pragma once
 
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IPendingIntentRef.h>
 #include <utils/RefBase.h>
+#include <utils/String16.h>
 
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // subscription
-#include "android/os/StatsDimensionsValue.h"
 #include "HashableDimensionKey.h"
 
 #include <mutex>
 #include <unordered_map>
 #include <vector>
 
+using aidl::android::os::IPendingIntentRef;
+using std::mutex;
+using std::shared_ptr;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -47,32 +54,17 @@
     void operator=(SubscriberReporter const&) = delete;
 
     /**
-     * Tells SubscriberReporter what IStatsCompanionService to use.
-     * May be nullptr, but SubscriberReporter will not send broadcasts for any calls
-     * to alertBroadcastSubscriber that occur while nullptr.
-     */
-    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
-        std::lock_guard<std::mutex> lock(mLock);
-        sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
-        mStatsCompanionService = statsCompanionService;
-    }
-
-    /**
      * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair.
-     * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
      */
     void setBroadcastSubscriber(const ConfigKey& configKey,
                                 int64_t subscriberId,
-                                const sp<android::IBinder>& intentSender);
+                                const shared_ptr<IPendingIntentRef>& pir);
 
     /**
      * Erases any intentSender information from the given (configKey, subscriberId) pair.
      */
     void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
 
-    /** Remove all information stored by SubscriberReporter about the given config. */
-    void removeConfig(const ConfigKey& configKey);
-
     /**
      * Sends a broadcast via the intentSender previously stored for the
      * given (configKey, subscriberId) pair by setBroadcastSubscriber.
@@ -82,29 +74,34 @@
                                   const Subscription& subscription,
                                   const MetricDimensionKey& dimKey) const;
 
-    static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim);
+    shared_ptr<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey,
+                                                         int64_t subscriberId);
 
 private:
-    SubscriberReporter() {};
+    SubscriberReporter();
 
-    mutable std::mutex mLock;
+    mutable mutex mLock;
 
-    /** Binder interface for communicating with StatsCompanionService. */
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
-
-    /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */
-    std::unordered_map<ConfigKey,
-            std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap;
+    /** Maps <ConfigKey, SubscriberId> -> IPendingIntentRef (which represents a PendingIntent). */
+    unordered_map<ConfigKey, unordered_map<int64_t, shared_ptr<IPendingIntentRef>>> mIntentMap;
 
     /**
      * Sends a broadcast via the given intentSender (using mStatsCompanionService), along
      * with the information in the other parameters.
      */
-    void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
+    void sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
                              const ConfigKey& configKey,
                              const Subscription& subscription,
-                             const std::vector<String16>& cookies,
+                             const vector<string>& cookies,
                              const MetricDimensionKey& dimKey) const;
+
+    ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a broadcast subscriber dies.
+     * The cookie is a pointer to a BroadcastSubscriberDeathCookie.
+     */
+    static void broadcastSubscriberDied(void* cookie);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
new file mode 100644
index 0000000..43a6933
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 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 false  // STOPSHIP if true
+
+#include "MultiConditionTrigger.h"
+
+#include <thread>
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames,
+                                             function<void()> trigger)
+    : mRemainingConditionNames(conditionNames),
+      mTrigger(trigger),
+      mCompleted(mRemainingConditionNames.empty()) {
+    if (mCompleted) {
+        thread executorThread([this] { mTrigger(); });
+        executorThread.detach();
+    }
+}
+
+void MultiConditionTrigger::markComplete(const string& conditionName) {
+    bool doTrigger = false;
+    {
+        lock_guard<mutex> lg(mMutex);
+        if (mCompleted) {
+            return;
+        }
+        mRemainingConditionNames.erase(conditionName);
+        mCompleted = mRemainingConditionNames.empty();
+        doTrigger = mCompleted;
+    }
+    if (doTrigger) {
+        std::thread executorThread([this] { mTrigger(); });
+        executorThread.detach();
+    }
+}
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.h b/cmds/statsd/src/utils/MultiConditionTrigger.h
new file mode 100644
index 0000000..51f6029
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gtest/gtest_prod.h>
+
+#include <mutex>
+#include <set>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * This class provides a utility to wait for a set of named conditions to occur.
+ *
+ * It will execute the trigger runnable in a detached thread once all conditions have been marked
+ * true.
+ */
+class MultiConditionTrigger {
+public:
+    explicit MultiConditionTrigger(const std::set<std::string>& conditionNames,
+                                   std::function<void()> trigger);
+
+    MultiConditionTrigger(const MultiConditionTrigger&) = delete;
+    MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete;
+
+    // Mark a specific condition as true. If this condition has called markComplete already or if
+    // the event was not specified in the constructor, the function is a no-op.
+    void markComplete(const std::string& eventName);
+
+private:
+    mutable std::mutex mMutex;
+    std::set<std::string> mRemainingConditionNames;
+    std::function<void()> mTrigger;
+    bool mCompleted;
+
+    FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName);
+};
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
deleted file mode 100644
index b87a483..0000000
--- a/cmds/statsd/statsd.rc
+++ /dev/null
@@ -1,24 +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.
-
-service statsd /system/bin/statsd
-    class main
-    socket statsdw dgram+passcred 0222 statsd statsd
-    user statsd
-    group statsd log
-    writepid /dev/cpuset/system-background/tasks
-
-on property:ro.statsd.enable=false
-    stop statsd
-
diff --git a/cmds/statsd/statsd_test.xml b/cmds/statsd/statsd_test.xml
new file mode 100644
index 0000000..8f9bb1c
--- /dev/null
+++ b/cmds/statsd/statsd_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Runs statsd_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+    <option name="test-suite-tag" value="mts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+       <option name="cleanup" value="true" />
+       <option name="push" value="statsd_test->/data/local/tmp/statsd_test" />
+       <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="statsd_test" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
+</configuration>
diff --git a/cmds/statsd/tests/AlarmMonitor_test.cpp b/cmds/statsd/tests/AlarmMonitor_test.cpp
index 1fccb35..1dc9795 100644
--- a/cmds/statsd/tests/AlarmMonitor_test.cpp
+++ b/cmds/statsd/tests/AlarmMonitor_test.cpp
@@ -17,14 +17,16 @@
 #include <gtest/gtest.h>
 
 using namespace android::os::statsd;
+using std::shared_ptr;
 
 #ifdef __ANDROID__
 TEST(AlarmMonitor, popSoonerThan) {
     std::string emptyMetricId;
     std::string emptyDimensionId;
     unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> set;
-    AlarmMonitor am(2, [](const sp<IStatsCompanionService>&, int64_t){},
-                    [](const sp<IStatsCompanionService>&){});
+    AlarmMonitor am(2,
+                    [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                    [](const shared_ptr<IStatsCompanionService>&){});
 
     set = am.popSoonerThan(5);
     EXPECT_TRUE(set.empty());
@@ -47,19 +49,19 @@
     EXPECT_TRUE(set.empty());
 
     set = am.popSoonerThan(30);
-    EXPECT_EQ(4u, set.size());
+    ASSERT_EQ(4u, set.size());
     EXPECT_EQ(1u, set.count(a));
     EXPECT_EQ(1u, set.count(b));
     EXPECT_EQ(1u, set.count(c));
     EXPECT_EQ(1u, set.count(d));
 
     set = am.popSoonerThan(60);
-    EXPECT_EQ(2u, set.size());
+    ASSERT_EQ(2u, set.size());
     EXPECT_EQ(1u, set.count(e));
     EXPECT_EQ(1u, set.count(f));
 
     set = am.popSoonerThan(80);
-    EXPECT_EQ(0u, set.size());
+    ASSERT_EQ(0u, set.size());
 }
 
 #else
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index f1cad92..a21eb9b 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -14,13 +14,16 @@
  * limitations under the License.
  */
 #include <gtest/gtest.h>
+
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/matcher_util.h"
 #include "src/logd/LogEvent.h"
+#include "stats_event.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
 #include "subscriber/SubscriberReporter.h"
+#include "tests/statsd_test_util.h"
 
 #ifdef __ANDROID__
 
@@ -30,6 +33,40 @@
 namespace os {
 namespace statsd {
 
+// These constants must be kept in sync with those in StatsDimensionsValue.java.
+const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
+const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3;
+const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6;
+const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7;
+
+namespace {
+void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                  const vector<int>& attributionUids, const vector<string>& attributionTags,
+                  const string& name) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, name.c_str());
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                  const vector<int>& attributionUids, const vector<string>& attributionTags,
+                  const int32_t value) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeInt32(statsEvent, value);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+}  // anonymous namespace
+
 TEST(AtomMatcherTest, TestFieldTranslation) {
     FieldMatcher matcher1;
     matcher1.set_field(10);
@@ -43,7 +80,7 @@
     vector<Matcher> output;
     translateFieldMatcher(matcher1, &output);
 
-    EXPECT_EQ((size_t)1, output.size());
+    ASSERT_EQ((size_t)1, output.size());
 
     const auto& matcher12 = output[0];
     EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
@@ -64,7 +101,7 @@
     vector<Matcher> output;
     translateFieldMatcher(matcher1, &output);
 
-    EXPECT_EQ((size_t)1, output.size());
+    ASSERT_EQ((size_t)1, output.size());
 
     const auto& matcher12 = output[0];
     EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
@@ -88,31 +125,16 @@
     vector<Matcher> matchers;
     translateFieldMatcher(matcher1, &matchers);
 
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1111);
-    attribution_node1.set_tag("location1");
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
 
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    AttributionNodeInternal attribution_node3;
-    attribution_node3.set_uid(3333);
-    attribution_node3.set_tag("location3");
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
-                                                              attribution_node3};
-
-    // Set up the event
-    LogEvent event(10, 12345);
-    event.write(attribution_nodes);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event, 10 /*atomId*/, 1012345, attributionUids, attributionTags, "some value");
     HashableDimensionKey output;
 
     filterValues(matchers, event.getValues(), &output);
 
-    EXPECT_EQ((size_t)7, output.getValues().size());
+    ASSERT_EQ((size_t)7, output.getValues().size());
     EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField());
     EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value);
     EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField());
@@ -174,26 +196,11 @@
 }
 
 TEST(AtomMatcherTest, TestMetric2ConditionLink) {
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1111);
-    attribution_node1.set_tag("location1");
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
 
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    AttributionNodeInternal attribution_node3;
-    attribution_node3.set_uid(3333);
-    attribution_node3.set_tag("location3");
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
-                                                              attribution_node3};
-
-    // Set up the event
-    LogEvent event(10, 12345);
-    event.write(attribution_nodes);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event, 10 /*atomId*/, 12345, attributionUids, attributionTags, "some value");
 
     FieldMatcher whatMatcher;
     whatMatcher.set_field(10);
@@ -217,12 +224,12 @@
     translateFieldMatcher(whatMatcher, &link.metricFields);
     translateFieldMatcher(conditionMatcher, &link.conditionFields);
 
-    EXPECT_EQ((size_t)1, link.metricFields.size());
+    ASSERT_EQ((size_t)1, link.metricFields.size());
     EXPECT_EQ((int32_t)0x02010001, link.metricFields[0].mMatcher.getField());
     EXPECT_EQ((int32_t)0xff7f007f, link.metricFields[0].mMask);
     EXPECT_EQ((int32_t)10, link.metricFields[0].mMatcher.getTag());
 
-    EXPECT_EQ((size_t)1, link.conditionFields.size());
+    ASSERT_EQ((size_t)1, link.conditionFields.size());
     EXPECT_EQ((int32_t)0x02028002, link.conditionFields[0].mMatcher.getField());
     EXPECT_EQ((int32_t)0xff7f807f, link.conditionFields[0].mMask);
     EXPECT_EQ((int32_t)27, link.conditionFields[0].mMatcher.getTag());
@@ -263,15 +270,15 @@
         }
 
         DimensionsValue result;
-        EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
+        ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
 
         EXPECT_EQ(10, result.field());
         EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case());
-        EXPECT_EQ(3, result.value_tuple().dimensions_value_size());
+        ASSERT_EQ(3, result.value_tuple().dimensions_value_size());
 
         const auto& dim1 = result.value_tuple().dimensions_value(0);
         EXPECT_EQ(2, dim1.field());
-        EXPECT_EQ(2, dim1.value_tuple().dimensions_value_size());
+        ASSERT_EQ(2, dim1.value_tuple().dimensions_value_size());
 
         const auto& dim11 = dim1.value_tuple().dimensions_value(0);
         EXPECT_EQ(1, dim11.field());
@@ -284,38 +291,81 @@
 
         const auto& dim3 = result.value_tuple().dimensions_value(2);
         EXPECT_EQ(6, dim3.field());
-        EXPECT_EQ(1, dim3.value_tuple().dimensions_value_size());
+        ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size());
         const auto& dim31 = dim3.value_tuple().dimensions_value(0);
         EXPECT_EQ(2, dim31.field());
     }
 }
 
-TEST(AtomMatcherTest, TestSubscriberDimensionWrite) {
-    HashableDimensionKey dim;
+void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel,
+                                                 int32_t nodeDepthInAttributionChain,
+                                                 int32_t uid, string tag) {
+    EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/);
+    ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE);
+    ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2);
 
+    StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0];
+    EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/);
+    EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE);
+    EXPECT_EQ(uidParcel.intValue, uid);
+
+    StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1];
+    EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/);
+    EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE);
+    EXPECT_EQ(tagParcel.stringValue, tag);
+}
+
+// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel
+TEST(AtomMatcherTest, TestSubscriberDimensionWrite) {
+    int atomId = 10;
+    // First four fields form an attribution chain
     int pos1[] = {1, 1, 1};
     int pos2[] = {1, 1, 2};
-    int pos3[] = {1, 1, 3};
-    int pos4[] = {2, 0, 0};
+    int pos3[] = {1, 2, 1};
+    int pos4[] = {1, 2, 2};
+    int pos5[] = {2, 1, 1};
 
-    Field field1(10, pos1, 2);
-    Field field2(10, pos2, 2);
-    Field field3(10, pos3, 2);
-    Field field4(10, pos4, 0);
+    Field field1(atomId, pos1, /*depth=*/2);
+    Field field2(atomId, pos2, /*depth=*/2);
+    Field field3(atomId, pos3, /*depth=*/2);
+    Field field4(atomId, pos4, /*depth=*/2);
+    Field field5(atomId, pos5, /*depth=*/0);
 
-    Value value1((int32_t)10025);
-    Value value2("tag");
-    Value value3((int32_t)987654);
-    Value value4((int32_t)99999);
+    Value value1((int32_t)1);
+    Value value2("string2");
+    Value value3((int32_t)3);
+    Value value4("string4");
+    Value value5((float)5.0);
 
-    dim.addValue(FieldValue(field1, value1));
-    dim.addValue(FieldValue(field2, value2));
-    dim.addValue(FieldValue(field3, value3));
-    dim.addValue(FieldValue(field4, value4));
+    HashableDimensionKey dimensionKey;
+    dimensionKey.addValue(FieldValue(field1, value1));
+    dimensionKey.addValue(FieldValue(field2, value2));
+    dimensionKey.addValue(FieldValue(field3, value3));
+    dimensionKey.addValue(FieldValue(field4, value4));
+    dimensionKey.addValue(FieldValue(field5, value5));
 
-    SubscriberReporter::getStatsDimensionsValue(dim);
-    // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't
-    // have any read api.
+    StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel();
+    EXPECT_EQ(rootParcel.field, atomId);
+    ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE);
+    ASSERT_EQ(rootParcel.tupleValue.size(), 2);
+
+    // Check that attribution chain is populated correctly
+    StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0];
+    EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/);
+    ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE);
+    ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2);
+    checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0],
+                                                /*nodeDepthInAttributionChain=*/1,
+                                                value1.int_value, value2.str_value);
+    checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1],
+                                                /*nodeDepthInAttributionChain=*/2,
+                                                value3.int_value, value4.str_value);
+
+    // Check that the float is populated correctly
+    StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1];
+    EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/);
+    EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE);
+    EXPECT_EQ(floatParcel.floatValue, value5.float_value);
 }
 
 TEST(AtomMatcherTest, TestWriteDimensionToProto) {
@@ -354,14 +404,14 @@
     }
 
     DimensionsValue result;
-    EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
+    ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
     EXPECT_EQ(10, result.field());
     EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case());
-    EXPECT_EQ(2, result.value_tuple().dimensions_value_size());
+    ASSERT_EQ(2, result.value_tuple().dimensions_value_size());
 
     const auto& dim1 = result.value_tuple().dimensions_value(0);
     EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, dim1.value_case());
-    EXPECT_EQ(3, dim1.value_tuple().dimensions_value_size());
+    ASSERT_EQ(3, dim1.value_tuple().dimensions_value_size());
 
     const auto& dim11 = dim1.value_tuple().dimensions_value(0);
     EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim11.value_case());
@@ -416,8 +466,8 @@
     }
 
     DimensionsValueTuple result;
-    EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
-    EXPECT_EQ(4, result.dimensions_value_size());
+    ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
+    ASSERT_EQ(4, result.dimensions_value_size());
 
     const auto& dim1 = result.dimensions_value(0);
     EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim1.value_case());
@@ -437,22 +487,11 @@
 }
 
 TEST(AtomMatcherTest, TestWriteAtomToProto) {
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1111);
-    attribution_node1.set_tag("location1");
+    std::vector<int> attributionUids = {1111, 2222};
+    std::vector<string> attributionTags = {"location1", "location2"};
 
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2};
-
-    // Set up the event
-    LogEvent event(4, 12345);
-    event.write(attribution_nodes);
-    event.write((int32_t)999);
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event, 4 /*atomId*/, 12345, attributionUids, attributionTags, 999);
 
     android::util::ProtoOutputStream protoOutput;
     writeFieldValueTreeToStream(event.GetTagId(), event.getValues(), &protoOutput);
@@ -469,10 +508,10 @@
     }
 
     Atom result;
-    EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
+    ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
     EXPECT_EQ(Atom::PushedCase::kBleScanResultReceived, result.pushed_case());
     const auto& atom = result.ble_scan_result_received();
-    EXPECT_EQ(2, atom.attribution_node_size());
+    ASSERT_EQ(2, atom.attribution_node_size());
     EXPECT_EQ(1111, atom.attribution_node(0).uid());
     EXPECT_EQ("location1", atom.attribution_node(0).tag());
     EXPECT_EQ(2222, atom.attribution_node(1).uid());
@@ -480,6 +519,137 @@
     EXPECT_EQ(999, atom.num_results());
 }
 
+/*
+ * Test two Matchers is not a subset of one Matcher.
+ * Test one Matcher is subset of two Matchers.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions1) {
+    // Initialize first set of matchers
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::ALL);
+    child->add_child()->set_field(1);
+    child->add_child()->set_field(2);
+
+    vector<Matcher> matchers1;
+    translateFieldMatcher(matcher1, &matchers1);
+    ASSERT_EQ(2, matchers1.size());
+
+    // Initialize second set of matchers
+    FieldMatcher matcher2;
+    matcher2.set_field(10);
+
+    child = matcher2.add_child();
+    child->set_field(1);
+    child->set_position(Position::ALL);
+    child->add_child()->set_field(1);
+
+    vector<Matcher> matchers2;
+    translateFieldMatcher(matcher2, &matchers2);
+    ASSERT_EQ(1, matchers2.size());
+
+    EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+    EXPECT_TRUE(subsetDimensions(matchers2, matchers1));
+}
+/*
+ * Test not a subset with one matching Matcher, one non-matching Matcher.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions2) {
+    // Initialize first set of matchers
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+
+    child = matcher1.add_child();
+    child->set_field(2);
+
+    vector<Matcher> matchers1;
+    translateFieldMatcher(matcher1, &matchers1);
+
+    // Initialize second set of matchers
+    FieldMatcher matcher2;
+    matcher2.set_field(10);
+
+    child = matcher2.add_child();
+    child->set_field(1);
+
+    child = matcher2.add_child();
+    child->set_field(3);
+
+    vector<Matcher> matchers2;
+    translateFieldMatcher(matcher2, &matchers2);
+
+    EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+}
+
+/*
+ * Test not a subset if parent field is not equal.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions3) {
+    // Initialize first set of matchers
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+
+    vector<Matcher> matchers1;
+    translateFieldMatcher(matcher1, &matchers1);
+
+    // Initialize second set of matchers
+    FieldMatcher matcher2;
+    matcher2.set_field(5);
+
+    child = matcher2.add_child();
+    child->set_field(1);
+
+    vector<Matcher> matchers2;
+    translateFieldMatcher(matcher2, &matchers2);
+
+    EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+}
+
+/*
+ * Test is subset with two matching Matchers.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions4) {
+    // Initialize first set of matchers
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+
+    child = matcher1.add_child();
+    child->set_field(2);
+
+    vector<Matcher> matchers1;
+    translateFieldMatcher(matcher1, &matchers1);
+
+    // Initialize second set of matchers
+    FieldMatcher matcher2;
+    matcher2.set_field(10);
+
+    child = matcher2.add_child();
+    child->set_field(1);
+
+    child = matcher2.add_child();
+    child->set_field(2);
+
+    child = matcher2.add_child();
+    child->set_field(3);
+
+    vector<Matcher> matchers2;
+    translateFieldMatcher(matcher2, &matchers2);
+
+    EXPECT_TRUE(subsetDimensions(matchers1, matchers2));
+    EXPECT_FALSE(subsetDimensions(matchers2, matchers1));
+}
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp
new file mode 100644
index 0000000..29adcd0
--- /dev/null
+++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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 "src/HashableDimensionKey.h"
+
+#include <gtest/gtest.h>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using android::util::ProtoReader;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Test that #containsLinkedStateValues returns false when the whatKey is
+ * smaller than the primaryKey.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) {
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY;
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks,
+                                           UID_PROCESS_STATE_ATOM_ID));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when the linked values
+ * are not equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    int32_t uid2 = 1001;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid2, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when there is no link
+ * between the key values.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns true when the key values are
+ * linked and equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 2cbcf28..6264c07 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -12,20 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include "annotations.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/matcher_util.h"
+#include "stats_event.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
-
-#include <gtest/gtest.h>
-
-#include <stdio.h>
+#include "statsd_test_util.h"
 
 using namespace android::os::statsd;
 using std::unordered_map;
 using std::vector;
 
 const int32_t TAG_ID = 123;
+const int32_t TAG_ID_2 = 28;  // hardcoded tag of atom with uid field
 const int FIELD_ID_1 = 1;
 const int FIELD_ID_2 = 2;
 const int FIELD_ID_3 = 2;
@@ -35,6 +38,77 @@
 
 
 #ifdef __ANDROID__
+
+namespace {
+
+void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                     const int32_t value) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+    AStatsEvent_writeInt32(statsEvent, value);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                       const float floatValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+    AStatsEvent_writeFloat(statsEvent, floatValue);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                        const string& name) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+    AStatsEvent_writeString(statsEvent, name.c_str());
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId,
+                                       const int32_t field, const uint8_t annotationId,
+                                       const bool annotationValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_writeInt32(statsEvent, field);
+    AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                             const vector<int>& attributionUids,
+                             const vector<string>& attributionTags, const string& name) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, name.c_str());
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
+                      const bool bool1, const bool bool2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+    AStatsEvent_writeBool(statsEvent, bool1);
+    AStatsEvent_writeBool(statsEvent, bool2);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+}  // anonymous namespace
+
 TEST(AtomMatcherTest, TestSimpleMatcher) {
     UidMap uidMap;
 
@@ -43,9 +117,8 @@
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
     simpleMatcher->set_atom_id(TAG_ID);
 
-    LogEvent event(TAG_ID, 0);
-    EXPECT_TRUE(event.write(11));
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeIntLogEvent(&event, TAG_ID, 0, 11);
 
     // Test
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
@@ -57,26 +130,12 @@
 
 TEST(AtomMatcherTest, TestAttributionMatcher) {
     UidMap uidMap;
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1111);
-    attribution_node1.set_tag("location1");
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
 
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    AttributionNodeInternal attribution_node3;
-    attribution_node3.set_uid(3333);
-    attribution_node3.set_tag("location3");
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
-                                                              attribution_node3};
-
-    // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write(attribution_nodes);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value");
 
     // Set up the matcher
     AtomMatcher matcher;
@@ -88,8 +147,9 @@
     attributionMatcher->set_field(FIELD_ID_1);
     attributionMatcher->set_position(Position::FIRST);
     attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
-        ATTRIBUTION_TAG_FIELD_ID);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag");
+            ATTRIBUTION_TAG_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "tag");
 
     auto fieldMatcher = simpleMatcher->add_field_value_matcher();
     fieldMatcher->set_field(FIELD_ID_2);
@@ -97,40 +157,40 @@
 
     // Tag not matched.
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location3");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Match last node.
     attributionMatcher->set_position(Position::LAST);
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Match any node.
     attributionMatcher->set_position(Position::ANY);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location2");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location4");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location4");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Attribution match but primitive field not match.
     attributionMatcher->set_position(Position::ANY);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("location2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "location2");
     fieldMatcher->set_eq_string("wrong value");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
@@ -139,8 +199,9 @@
     // Uid match.
     attributionMatcher->set_position(Position::ANY);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field(
-        ATTRIBUTION_UID_FIELD_ID);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0");
+            ATTRIBUTION_UID_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     uidMap.updateMap(
@@ -153,147 +214,183 @@
              android::String16(""), android::String16("")});
 
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     attributionMatcher->set_position(Position::FIRST);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     attributionMatcher->set_position(Position::LAST);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Uid + tag.
     attributionMatcher->set_position(Position::ANY);
     attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
-        ATTRIBUTION_TAG_FIELD_ID);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+            ATTRIBUTION_TAG_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location2");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     attributionMatcher->set_position(Position::FIRST);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location2");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     attributionMatcher->set_position(Position::LAST);
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg0");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg1");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location2");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg2");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location3");
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
-        ->set_eq_string("pkg3");
-    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
-        ->set_eq_string("location1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
+            "location1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
+TEST(AtomMatcherTest, TestUidFieldMatcher) {
+    UidMap uidMap;
+    uidMap.updateMap(
+            1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+            {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+             android::String16("v1"), android::String16("v2")},
+            {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
+             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+            {android::String16(""), android::String16(""), android::String16(""),
+             android::String16(""), android::String16("")});
+
+    // Set up matcher
+    AtomMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+    simpleMatcher->add_field_value_matcher()->set_field(1);
+    simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0");
+
+    // Make event without is_uid annotation.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeIntLogEvent(&event1, TAG_ID, 0, 1111);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+
+    // Make event with is_uid annotation.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true);
+
+    // Event has is_uid annotation, so mapping from uid to package name occurs.
+    simpleMatcher->set_atom_id(TAG_ID_2);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+
+    // Event has is_uid annotation, but uid maps to different package name.
+    simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+}
+
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher) {
     UidMap uidMap;
     uidMap.updateMap(
@@ -305,30 +402,12 @@
             {android::String16(""), android::String16(""), android::String16(""),
              android::String16(""), android::String16("")});
 
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1111);
-    attribution_node1.set_tag("location1");
-
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    AttributionNodeInternal attribution_node3;
-    attribution_node3.set_uid(3333);
-    attribution_node3.set_tag("location3");
-
-    AttributionNodeInternal attribution_node4;
-    attribution_node4.set_uid(1066);
-    attribution_node4.set_tag("location3");
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
-                                                              attribution_node3, attribution_node4};
+    std::vector<int> attributionUids = {1111, 2222, 3333, 1066};
+    std::vector<string> attributionTags = {"location1", "location2", "location3", "location3"};
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write(attribution_nodes);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value");
 
     // Set up the matcher
     AtomMatcher matcher;
@@ -384,30 +463,12 @@
             {android::String16(""), android::String16(""), android::String16(""),
              android::String16(""), android::String16("")});
 
-    AttributionNodeInternal attribution_node1;
-    attribution_node1.set_uid(1067);
-    attribution_node1.set_tag("location1");
-
-    AttributionNodeInternal attribution_node2;
-    attribution_node2.set_uid(2222);
-    attribution_node2.set_tag("location2");
-
-    AttributionNodeInternal attribution_node3;
-    attribution_node3.set_uid(3333);
-    attribution_node3.set_tag("location3");
-
-    AttributionNodeInternal attribution_node4;
-    attribution_node4.set_uid(1066);
-    attribution_node4.set_tag("location3");
-    std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
-                                                              attribution_node3, attribution_node4};
+    std::vector<int> attributionUids = {1067, 2222, 3333, 1066};
+    std::vector<string> attributionTags = {"location1", "location2", "location3", "location3"};
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write(attribution_nodes);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value");
 
     // Set up the matcher
     AtomMatcher matcher;
@@ -467,11 +528,8 @@
     keyValue2->set_field(FIELD_ID_2);
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    EXPECT_TRUE(event.write(true));
-    EXPECT_TRUE(event.write(false));
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeBoolLogEvent(&event, TAG_ID, 0, true, false);
 
     // Test
     keyValue1->set_eq_bool(true);
@@ -502,10 +560,8 @@
     keyValue->set_eq_string("some value");
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write("some value");
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeStringLogEvent(&event, TAG_ID, 0, "some value");
 
     // Test
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
@@ -523,12 +579,8 @@
     keyValue2->set_field(FIELD_ID_2);
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write(2);
-    event.write(3);
-
-    // Convert to a LogEvent
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(&event, TAG_ID, 0, 2, 3);
 
     // Test
     keyValue1->set_eq_int(2);
@@ -555,9 +607,8 @@
     keyValue->set_field(FIELD_ID_1);
 
     // Set up the event
-    LogEvent event(TAG_ID, 0);
-    event.write(11);
-    event.init();
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeIntLogEvent(&event, TAG_ID, 0, 11);
 
     // Test
 
@@ -612,26 +663,22 @@
     auto keyValue = simpleMatcher->add_field_value_matcher();
     keyValue->set_field(FIELD_ID_1);
 
-    LogEvent event1(TAG_ID, 0);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeFloatLogEvent(&event1, TAG_ID, 0, 10.1f);
     keyValue->set_lt_float(10.0);
-    event1.write(10.1f);
-    event1.init();
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
 
-    LogEvent event2(TAG_ID, 0);
-    event2.write(9.9f);
-    event2.init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeFloatLogEvent(&event2, TAG_ID, 0, 9.9f);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
-    LogEvent event3(TAG_ID, 0);
-    event3.write(10.1f);
-    event3.init();
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeFloatLogEvent(&event3, TAG_ID, 0, 10.1f);
     keyValue->set_gt_float(10.0);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
 
-    LogEvent event4(TAG_ID, 0);
-    event4.write(9.9f);
-    event4.init();
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    makeFloatLogEvent(&event4, TAG_ID, 0, 9.9f);
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
 }
 
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 504ee22..5c170c0 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -13,10 +13,13 @@
 // limitations under the License.
 
 #include "src/logd/LogEvent.h"
+
 #include <gtest/gtest.h>
-#include <log/log_event_list.h>
+
 #include "frameworks/base/cmds/statsd/src/atoms.pb.h"
 #include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
+#include "log/log_event_list.h"
+#include "stats_event.h"
 
 #ifdef __ANDROID__
 
@@ -25,642 +28,341 @@
 namespace statsd {
 
 using std::string;
+using std::vector;
 using util::ProtoOutputStream;
 using util::ProtoReader;
 
-TEST(LogEventTest, TestLogParsing) {
-    LogEvent event1(1, 2000);
+namespace {
 
-    std::vector<AttributionNodeInternal> nodes;
+Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) {
+    Field f(tag, (int32_t*)pos.data(), depth);
 
-    AttributionNodeInternal node1;
-    node1.set_uid(1000);
-    node1.set_tag("tag1");
-    nodes.push_back(node1);
+    // For loop starts at 1 because the last field at depth 0 is not decorated.
+    for (int i = 1; i < depth; i++) {
+        if (last[i]) f.decorateLastPos(i);
+    }
 
-    AttributionNodeInternal node2;
-    node2.set_uid(2000);
-    node2.set_tag("tag2");
-    nodes.push_back(node2);
-
-    event1.write(nodes);
-    event1.write("hello");
-    event1.write((int32_t)10);
-    event1.write((int64_t)20);
-    event1.write((float)1.1);
-    event1.init();
-
-    const auto& items = event1.getValues();
-    EXPECT_EQ((size_t)8, items.size());
-    EXPECT_EQ(1, event1.GetTagId());
-
-    const FieldValue& item0 = event1.getValues()[0];
-    EXPECT_EQ(0x2010101, item0.mField.getField());
-    EXPECT_EQ(Type::INT, item0.mValue.getType());
-    EXPECT_EQ(1000, item0.mValue.int_value);
-
-    const FieldValue& item1 = event1.getValues()[1];
-    EXPECT_EQ(0x2010182, item1.mField.getField());
-    EXPECT_EQ(Type::STRING, item1.mValue.getType());
-    EXPECT_EQ("tag1", item1.mValue.str_value);
-
-    const FieldValue& item2 = event1.getValues()[2];
-    EXPECT_EQ(0x2018201, item2.mField.getField());
-    EXPECT_EQ(Type::INT, item2.mValue.getType());
-    EXPECT_EQ(2000, item2.mValue.int_value);
-
-    const FieldValue& item3 = event1.getValues()[3];
-    EXPECT_EQ(0x2018282, item3.mField.getField());
-    EXPECT_EQ(Type::STRING, item3.mValue.getType());
-    EXPECT_EQ("tag2", item3.mValue.str_value);
-
-    const FieldValue& item4 = event1.getValues()[4];
-    EXPECT_EQ(0x20000, item4.mField.getField());
-    EXPECT_EQ(Type::STRING, item4.mValue.getType());
-    EXPECT_EQ("hello", item4.mValue.str_value);
-
-    const FieldValue& item5 = event1.getValues()[5];
-    EXPECT_EQ(0x30000, item5.mField.getField());
-    EXPECT_EQ(Type::INT, item5.mValue.getType());
-    EXPECT_EQ(10, item5.mValue.int_value);
-
-    const FieldValue& item6 = event1.getValues()[6];
-    EXPECT_EQ(0x40000, item6.mField.getField());
-    EXPECT_EQ(Type::LONG, item6.mValue.getType());
-    EXPECT_EQ((int64_t)20, item6.mValue.long_value);
-
-    const FieldValue& item7 = event1.getValues()[7];
-    EXPECT_EQ(0x50000, item7.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item7.mValue.getType());
-    EXPECT_EQ((float)1.1, item7.mValue.float_value);
+    return f;
 }
 
-TEST(LogEventTest, TestKeyValuePairsAtomParsing) {
-    LogEvent event1(83, 2000, 1000);
-    std::map<int32_t, int32_t> int_map;
-    std::map<int32_t, int64_t> long_map;
-    std::map<int32_t, std::string> string_map;
-    std::map<int32_t, float> float_map;
+void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+                                         bool annotationValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+    AStatsEvent_build(statsEvent);
 
-    int_map[11] = 123;
-    int_map[22] = 345;
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
 
-    long_map[33] = 678L;
-    long_map[44] = 890L;
-
-    string_map[1] = "test2";
-    string_map[2] = "test1";
-
-    float_map[111] = 2.2f;
-    float_map[222] = 1.1f;
-
-    EXPECT_TRUE(event1.writeKeyValuePairs(0, // Logging side logs 0 uid.
-                                          int_map,
-                                          long_map,
-                                          string_map,
-                                          float_map));
-    event1.init();
-
-    EXPECT_EQ(83, event1.GetTagId());
-    const auto& items = event1.getValues();
-    EXPECT_EQ((size_t)17, items.size());
-
-    const FieldValue& item0 = event1.getValues()[0];
-    EXPECT_EQ(0x10000, item0.mField.getField());
-    EXPECT_EQ(Type::INT, item0.mValue.getType());
-    EXPECT_EQ(1000, item0.mValue.int_value);
-
-    const FieldValue& item1 = event1.getValues()[1];
-    EXPECT_EQ(0x2010201, item1.mField.getField());
-    EXPECT_EQ(Type::INT, item1.mValue.getType());
-    EXPECT_EQ(11, item1.mValue.int_value);
-
-    const FieldValue& item2 = event1.getValues()[2];
-    EXPECT_EQ(0x2010282, item2.mField.getField());
-    EXPECT_EQ(Type::INT, item2.mValue.getType());
-    EXPECT_EQ(123, item2.mValue.int_value);
-
-    const FieldValue& item3 = event1.getValues()[3];
-    EXPECT_EQ(0x2010301, item3.mField.getField());
-    EXPECT_EQ(Type::INT, item3.mValue.getType());
-    EXPECT_EQ(22, item3.mValue.int_value);
-
-    const FieldValue& item4 = event1.getValues()[4];
-    EXPECT_EQ(0x2010382, item4.mField.getField());
-    EXPECT_EQ(Type::INT, item4.mValue.getType());
-    EXPECT_EQ(345, item4.mValue.int_value);
-
-    const FieldValue& item5 = event1.getValues()[5];
-    EXPECT_EQ(0x2010401, item5.mField.getField());
-    EXPECT_EQ(Type::INT, item5.mValue.getType());
-    EXPECT_EQ(33, item5.mValue.int_value);
-
-    const FieldValue& item6 = event1.getValues()[6];
-    EXPECT_EQ(0x2010483, item6.mField.getField());
-    EXPECT_EQ(Type::LONG, item6.mValue.getType());
-    EXPECT_EQ(678L, item6.mValue.int_value);
-
-    const FieldValue& item7 = event1.getValues()[7];
-    EXPECT_EQ(0x2010501, item7.mField.getField());
-    EXPECT_EQ(Type::INT, item7.mValue.getType());
-    EXPECT_EQ(44, item7.mValue.int_value);
-
-    const FieldValue& item8 = event1.getValues()[8];
-    EXPECT_EQ(0x2010583, item8.mField.getField());
-    EXPECT_EQ(Type::LONG, item8.mValue.getType());
-    EXPECT_EQ(890L, item8.mValue.int_value);
-
-    const FieldValue& item9 = event1.getValues()[9];
-    EXPECT_EQ(0x2010601, item9.mField.getField());
-    EXPECT_EQ(Type::INT, item9.mValue.getType());
-    EXPECT_EQ(1, item9.mValue.int_value);
-
-    const FieldValue& item10 = event1.getValues()[10];
-    EXPECT_EQ(0x2010684, item10.mField.getField());
-    EXPECT_EQ(Type::STRING, item10.mValue.getType());
-    EXPECT_EQ("test2", item10.mValue.str_value);
-
-    const FieldValue& item11 = event1.getValues()[11];
-    EXPECT_EQ(0x2010701, item11.mField.getField());
-    EXPECT_EQ(Type::INT, item11.mValue.getType());
-    EXPECT_EQ(2, item11.mValue.int_value);
-
-    const FieldValue& item12 = event1.getValues()[12];
-    EXPECT_EQ(0x2010784, item12.mField.getField());
-    EXPECT_EQ(Type::STRING, item12.mValue.getType());
-    EXPECT_EQ("test1", item12.mValue.str_value);
-
-    const FieldValue& item13 = event1.getValues()[13];
-    EXPECT_EQ(0x2010801, item13.mField.getField());
-    EXPECT_EQ(Type::INT, item13.mValue.getType());
-    EXPECT_EQ(111, item13.mValue.int_value);
-
-    const FieldValue& item14 = event1.getValues()[14];
-    EXPECT_EQ(0x2010885, item14.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item14.mValue.getType());
-    EXPECT_EQ(2.2f, item14.mValue.float_value);
-
-    const FieldValue& item15 = event1.getValues()[15];
-    EXPECT_EQ(0x2018901, item15.mField.getField());
-    EXPECT_EQ(Type::INT, item15.mValue.getType());
-    EXPECT_EQ(222, item15.mValue.int_value);
-
-    const FieldValue& item16 = event1.getValues()[16];
-    EXPECT_EQ(0x2018985, item16.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item16.mValue.getType());
-    EXPECT_EQ(1.1f, item16.mValue.float_value);
+    AStatsEvent_release(statsEvent);
 }
 
-TEST(LogEventTest, TestLogParsing2) {
-    LogEvent event1(1, 2000);
+void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+                                        int annotationValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+    AStatsEvent_build(statsEvent);
 
-    std::vector<AttributionNodeInternal> nodes;
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
 
-    event1.write("hello");
+    AStatsEvent_release(statsEvent);
+}
 
-    // repeated msg can be in the middle
-    AttributionNodeInternal node1;
-    node1.set_uid(1000);
-    node1.set_tag("tag1");
-    nodes.push_back(node1);
+}  // anonymous namespace
 
-    AttributionNodeInternal node2;
-    node2.set_uid(2000);
-    node2.set_tag("tag2");
-    nodes.push_back(node2);
-    event1.write(nodes);
+TEST(LogEventTest, TestPrimitiveParsing) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_writeInt64(event, 0x123456789);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_writeBool(event, true);
+    AStatsEvent_build(event);
 
-    event1.write((int32_t)10);
-    event1.write((int64_t)20);
-    event1.write((float)1.1);
-    event1.init();
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
-    const auto& items = event1.getValues();
-    EXPECT_EQ((size_t)8, items.size());
-    EXPECT_EQ(1, event1.GetTagId());
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
 
-    const FieldValue& item = event1.getValues()[0];
-    EXPECT_EQ(0x00010000, item.mField.getField());
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+    EXPECT_FALSE(logEvent.hasAttributionChain());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(4, values.size());
+
+    const FieldValue& int32Item = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false});
+    EXPECT_EQ(expectedField, int32Item.mField);
+    EXPECT_EQ(Type::INT, int32Item.mValue.getType());
+    EXPECT_EQ(10, int32Item.mValue.int_value);
+
+    const FieldValue& int64Item = values[1];
+    expectedField = getField(100, {2, 1, 1}, 0, {false, false, false});
+    EXPECT_EQ(expectedField, int64Item.mField);
+    EXPECT_EQ(Type::LONG, int64Item.mValue.getType());
+    EXPECT_EQ(0x123456789, int64Item.mValue.long_value);
+
+    const FieldValue& floatItem = values[2];
+    expectedField = getField(100, {3, 1, 1}, 0, {false, false, false});
+    EXPECT_EQ(expectedField, floatItem.mField);
+    EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType());
+    EXPECT_EQ(2.0, floatItem.mValue.float_value);
+
+    const FieldValue& boolItem = values[3];
+    expectedField = getField(100, {4, 1, 1}, 0, {true, false, false});
+    EXPECT_EQ(expectedField, boolItem.mField);
+    EXPECT_EQ(Type::INT, boolItem.mValue.getType());  // FieldValue does not support boolean type
+    EXPECT_EQ(1, boolItem.mValue.int_value);
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestStringAndByteArrayParsing) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    string str = "test";
+    AStatsEvent_writeString(event, str.c_str());
+    AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length());
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+    EXPECT_FALSE(logEvent.hasAttributionChain());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(2, values.size());
+
+    const FieldValue& stringItem = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false});
+    EXPECT_EQ(expectedField, stringItem.mField);
+    EXPECT_EQ(Type::STRING, stringItem.mValue.getType());
+    EXPECT_EQ(str, stringItem.mValue.str_value);
+
+    const FieldValue& storageItem = values[1];
+    expectedField = getField(100, {2, 1, 1}, 0, {true, false, false});
+    EXPECT_EQ(expectedField, storageItem.mField);
+    EXPECT_EQ(Type::STORAGE, storageItem.mValue.getType());
+    vector<uint8_t> expectedValue = {'t', 'e', 's', 't'};
+    EXPECT_EQ(expectedValue, storageItem.mValue.storage_value);
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestEmptyString) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    string empty = "";
+    AStatsEvent_writeString(event, empty.c_str());
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+    EXPECT_FALSE(logEvent.hasAttributionChain());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(1, values.size());
+
+    const FieldValue& item = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false});
+    EXPECT_EQ(expectedField, item.mField);
     EXPECT_EQ(Type::STRING, item.mValue.getType());
-    EXPECT_EQ("hello", item.mValue.str_value);
+    EXPECT_EQ(empty, item.mValue.str_value);
 
-    const FieldValue& item0 = event1.getValues()[1];
-    EXPECT_EQ(0x2020101, item0.mField.getField());
-    EXPECT_EQ(Type::INT, item0.mValue.getType());
-    EXPECT_EQ(1000, item0.mValue.int_value);
-
-    const FieldValue& item1 = event1.getValues()[2];
-    EXPECT_EQ(0x2020182, item1.mField.getField());
-    EXPECT_EQ(Type::STRING, item1.mValue.getType());
-    EXPECT_EQ("tag1", item1.mValue.str_value);
-
-    const FieldValue& item2 = event1.getValues()[3];
-    EXPECT_EQ(0x2028201, item2.mField.getField());
-    EXPECT_EQ(Type::INT, item2.mValue.getType());
-    EXPECT_EQ(2000, item2.mValue.int_value);
-
-    const FieldValue& item3 = event1.getValues()[4];
-    EXPECT_EQ(0x2028282, item3.mField.getField());
-    EXPECT_EQ(Type::STRING, item3.mValue.getType());
-    EXPECT_EQ("tag2", item3.mValue.str_value);
-
-    const FieldValue& item5 = event1.getValues()[5];
-    EXPECT_EQ(0x30000, item5.mField.getField());
-    EXPECT_EQ(Type::INT, item5.mValue.getType());
-    EXPECT_EQ(10, item5.mValue.int_value);
-
-    const FieldValue& item6 = event1.getValues()[6];
-    EXPECT_EQ(0x40000, item6.mField.getField());
-    EXPECT_EQ(Type::LONG, item6.mValue.getType());
-    EXPECT_EQ((int64_t)20, item6.mValue.long_value);
-
-    const FieldValue& item7 = event1.getValues()[7];
-    EXPECT_EQ(0x50000, item7.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item7.mValue.getType());
-    EXPECT_EQ((float)1.1, item7.mValue.float_value);
+    AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestKeyValuePairsEvent) {
-    std::map<int32_t, int32_t> int_map;
-    std::map<int32_t, int64_t> long_map;
-    std::map<int32_t, std::string> string_map;
-    std::map<int32_t, float> float_map;
+TEST(LogEventTest, TestByteArrayWithNullCharacter) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
+    AStatsEvent_writeByteArray(event, message, 5);
+    AStatsEvent_build(event);
 
-    int_map[11] = 123;
-    int_map[22] = 345;
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
-    long_map[33] = 678L;
-    long_map[44] = 890L;
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
 
-    string_map[1] = "test2";
-    string_map[2] = "test1";
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
 
-    float_map[111] = 2.2f;
-    float_map[222] = 1.1f;
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(1, values.size());
 
-    LogEvent event1(83, 2000, 2001, 10001, int_map, long_map, string_map, float_map);
-    event1.init();
+    const FieldValue& item = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false});
+    EXPECT_EQ(expectedField, item.mField);
+    EXPECT_EQ(Type::STORAGE, item.mValue.getType());
+    vector<uint8_t> expectedValue(message, message + 5);
+    EXPECT_EQ(expectedValue, item.mValue.storage_value);
 
-    EXPECT_EQ(83, event1.GetTagId());
-    EXPECT_EQ((int64_t)2000, event1.GetLogdTimestampNs());
-    EXPECT_EQ((int64_t)2001, event1.GetElapsedTimestampNs());
-    EXPECT_EQ((int64_t)10001, event1.GetUid());
-
-    const auto& items = event1.getValues();
-    EXPECT_EQ((size_t)17, items.size());
-
-    const FieldValue& item0 = event1.getValues()[0];
-    EXPECT_EQ(0x00010000, item0.mField.getField());
-    EXPECT_EQ(Type::INT, item0.mValue.getType());
-    EXPECT_EQ(10001, item0.mValue.int_value);
-
-    const FieldValue& item1 = event1.getValues()[1];
-    EXPECT_EQ(0x2020101, item1.mField.getField());
-    EXPECT_EQ(Type::INT, item1.mValue.getType());
-    EXPECT_EQ(11, item1.mValue.int_value);
-
-    const FieldValue& item2 = event1.getValues()[2];
-    EXPECT_EQ(0x2020182, item2.mField.getField());
-    EXPECT_EQ(Type::INT, item2.mValue.getType());
-    EXPECT_EQ(123, item2.mValue.int_value);
-
-    const FieldValue& item3 = event1.getValues()[3];
-    EXPECT_EQ(0x2020201, item3.mField.getField());
-    EXPECT_EQ(Type::INT, item3.mValue.getType());
-    EXPECT_EQ(22, item3.mValue.int_value);
-
-    const FieldValue& item4 = event1.getValues()[4];
-    EXPECT_EQ(0x2020282, item4.mField.getField());
-    EXPECT_EQ(Type::INT, item4.mValue.getType());
-    EXPECT_EQ(345, item4.mValue.int_value);
-
-    const FieldValue& item5 = event1.getValues()[5];
-    EXPECT_EQ(0x2020301, item5.mField.getField());
-    EXPECT_EQ(Type::INT, item5.mValue.getType());
-    EXPECT_EQ(33, item5.mValue.int_value);
-
-    const FieldValue& item6 = event1.getValues()[6];
-    EXPECT_EQ(0x2020383, item6.mField.getField());
-    EXPECT_EQ(Type::LONG, item6.mValue.getType());
-    EXPECT_EQ(678L, item6.mValue.long_value);
-
-    const FieldValue& item7 = event1.getValues()[7];
-    EXPECT_EQ(0x2020401, item7.mField.getField());
-    EXPECT_EQ(Type::INT, item7.mValue.getType());
-    EXPECT_EQ(44, item7.mValue.int_value);
-
-    const FieldValue& item8 = event1.getValues()[8];
-    EXPECT_EQ(0x2020483, item8.mField.getField());
-    EXPECT_EQ(Type::LONG, item8.mValue.getType());
-    EXPECT_EQ(890L, item8.mValue.long_value);
-
-    const FieldValue& item9 = event1.getValues()[9];
-    EXPECT_EQ(0x2020501, item9.mField.getField());
-    EXPECT_EQ(Type::INT, item9.mValue.getType());
-    EXPECT_EQ(1, item9.mValue.int_value);
-
-    const FieldValue& item10 = event1.getValues()[10];
-    EXPECT_EQ(0x2020584, item10.mField.getField());
-    EXPECT_EQ(Type::STRING, item10.mValue.getType());
-    EXPECT_EQ("test2", item10.mValue.str_value);
-
-    const FieldValue& item11 = event1.getValues()[11];
-    EXPECT_EQ(0x2020601, item11.mField.getField());
-    EXPECT_EQ(Type::INT, item11.mValue.getType());
-    EXPECT_EQ(2, item11.mValue.int_value);
-
-    const FieldValue& item12 = event1.getValues()[12];
-    EXPECT_EQ(0x2020684, item12.mField.getField());
-    EXPECT_EQ(Type::STRING, item12.mValue.getType());
-    EXPECT_EQ("test1", item12.mValue.str_value);
-
-    const FieldValue& item13 = event1.getValues()[13];
-    EXPECT_EQ(0x2020701, item13.mField.getField());
-    EXPECT_EQ(Type::INT, item13.mValue.getType());
-    EXPECT_EQ(111, item13.mValue.int_value);
-
-    const FieldValue& item14 = event1.getValues()[14];
-    EXPECT_EQ(0x2020785, item14.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item14.mValue.getType());
-    EXPECT_EQ(2.2f, item14.mValue.float_value);
-
-    const FieldValue& item15 = event1.getValues()[15];
-    EXPECT_EQ(0x2028801, item15.mField.getField());
-    EXPECT_EQ(Type::INT, item15.mValue.getType());
-    EXPECT_EQ(222, item15.mValue.int_value);
-
-    const FieldValue& item16 = event1.getValues()[16];
-    EXPECT_EQ(0x2028885, item16.mField.getField());
-    EXPECT_EQ(Type::FLOAT, item16.mValue.getType());
-    EXPECT_EQ(1.1f, item16.mValue.float_value);
+    AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestStatsLogEventWrapperNoChain) {
-    Parcel parcel;
-    // tag id
-    parcel.writeInt32(1);
-    // elapsed realtime
-    parcel.writeInt64(1111L);
-    // wallclock time
-    parcel.writeInt64(2222L);
-    // no chain
-    parcel.writeInt32(0);
-    // 2 data
-    parcel.writeInt32(2);
-    // int 6
-    parcel.writeInt32(1);
-    parcel.writeInt32(6);
-    // long 10
-    parcel.writeInt32(2);
-    parcel.writeInt64(10);
-    parcel.setDataPosition(0);
+TEST(LogEventTest, TestAttributionChain) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
 
-    StatsLogEventWrapper statsLogEventWrapper;
-    EXPECT_EQ(NO_ERROR, statsLogEventWrapper.readFromParcel(&parcel));
-    EXPECT_EQ(1, statsLogEventWrapper.getTagId());
-    EXPECT_EQ(1111L, statsLogEventWrapper.getElapsedRealTimeNs());
-    EXPECT_EQ(2222L, statsLogEventWrapper.getWallClockTimeNs());
-    EXPECT_EQ(0, statsLogEventWrapper.getWorkChains().size());
-    EXPECT_EQ(2, statsLogEventWrapper.getElements().size());
-    EXPECT_EQ(6, statsLogEventWrapper.getElements()[0].int_value);
-    EXPECT_EQ(10L, statsLogEventWrapper.getElements()[1].long_value);
-    LogEvent event(statsLogEventWrapper, -1);
-    EXPECT_EQ(1, event.GetTagId());
-    EXPECT_EQ(1111L, event.GetElapsedTimestampNs());
-    EXPECT_EQ(2222L, event.GetLogdTimestampNs());
-    EXPECT_EQ(2, event.size());
-    EXPECT_EQ(6, event.getValues()[0].mValue.int_value);
-    EXPECT_EQ(10, event.getValues()[1].mValue.long_value);
+    string tag1 = "tag1";
+    string tag2 = "tag2";
+
+    uint32_t uids[] = {1001, 1002};
+    const char* tags[] = {tag1.c_str(), tag2.c_str()};
+
+    AStatsEvent_writeAttributionChain(event, uids, tags, 2);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(4, values.size());  // 2 per attribution node
+
+    std::pair<int, int> attrIndexRange;
+    EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange));
+    EXPECT_EQ(0, attrIndexRange.first);
+    EXPECT_EQ(3, attrIndexRange.second);
+
+    // Check first attribution node
+    const FieldValue& uid1Item = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 2, {true, false, false});
+    EXPECT_EQ(expectedField, uid1Item.mField);
+    EXPECT_EQ(Type::INT, uid1Item.mValue.getType());
+    EXPECT_EQ(1001, uid1Item.mValue.int_value);
+
+    const FieldValue& tag1Item = values[1];
+    expectedField = getField(100, {1, 1, 2}, 2, {true, false, true});
+    EXPECT_EQ(expectedField, tag1Item.mField);
+    EXPECT_EQ(Type::STRING, tag1Item.mValue.getType());
+    EXPECT_EQ(tag1, tag1Item.mValue.str_value);
+
+    // Check second attribution nodes
+    const FieldValue& uid2Item = values[2];
+    expectedField = getField(100, {1, 2, 1}, 2, {true, true, false});
+    EXPECT_EQ(expectedField, uid2Item.mField);
+    EXPECT_EQ(Type::INT, uid2Item.mValue.getType());
+    EXPECT_EQ(1002, uid2Item.mValue.int_value);
+
+    const FieldValue& tag2Item = values[3];
+    expectedField = getField(100, {1, 2, 2}, 2, {true, true, true});
+    EXPECT_EQ(expectedField, tag2Item.mField);
+    EXPECT_EQ(Type::STRING, tag2Item.mValue.getType());
+    EXPECT_EQ(tag2, tag2Item.mValue.str_value);
+
+    AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestStatsLogEventWrapperWithChain) {
-    Parcel parcel;
-    // tag id
-    parcel.writeInt32(1);
-    // elapsed realtime
-    parcel.writeInt64(1111L);
-    // wallclock time
-    parcel.writeInt64(2222L);
-    // 3 chains
-    parcel.writeInt32(3);
-    // chain1, 2 nodes (1, "tag1") (2, "tag2")
-    parcel.writeInt32(2);
-    parcel.writeInt32(1);
-    parcel.writeString16(String16("tag1"));
-    parcel.writeInt32(2);
-    parcel.writeString16(String16("tag2"));
-    // chain2, 1 node (3, "tag3")
-    parcel.writeInt32(1);
-    parcel.writeInt32(3);
-    parcel.writeString16(String16("tag3"));
-    // chain3, 2 nodes (4, "") (5, "")
-    parcel.writeInt32(2);
-    parcel.writeInt32(4);
-    parcel.writeString16(String16(""));
-    parcel.writeInt32(5);
-    parcel.writeString16(String16(""));
-    // 2 data
-    parcel.writeInt32(2);
-    // int 6
-    parcel.writeInt32(1);
-    parcel.writeInt32(6);
-    // long 10
-    parcel.writeInt32(2);
-    parcel.writeInt64(10);
-    parcel.setDataPosition(0);
+TEST(LogEventTest, TestAnnotationIdIsUid) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
 
-    StatsLogEventWrapper statsLogEventWrapper;
-    EXPECT_EQ(NO_ERROR, statsLogEventWrapper.readFromParcel(&parcel));
-    EXPECT_EQ(1, statsLogEventWrapper.getTagId());
-    EXPECT_EQ(1111L, statsLogEventWrapper.getElapsedRealTimeNs());
-    EXPECT_EQ(2222L, statsLogEventWrapper.getWallClockTimeNs());
-    EXPECT_EQ(3, statsLogEventWrapper.getWorkChains().size());
-    EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].uids.size());
-    EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[0].uids[0]);
-    EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].uids[1]);
-    EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].tags.size());
-    EXPECT_EQ("tag1", statsLogEventWrapper.getWorkChains()[0].tags[0]);
-    EXPECT_EQ("tag2", statsLogEventWrapper.getWorkChains()[0].tags[1]);
-    EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[1].uids.size());
-    EXPECT_EQ(3, statsLogEventWrapper.getWorkChains()[1].uids[0]);
-    EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[1].tags.size());
-    EXPECT_EQ("tag3", statsLogEventWrapper.getWorkChains()[1].tags[0]);
-    EXPECT_EQ(2, statsLogEventWrapper.getElements().size());
-    EXPECT_EQ(6, statsLogEventWrapper.getElements()[0].int_value);
-    EXPECT_EQ(10L, statsLogEventWrapper.getElements()[1].long_value);
-    EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[2].uids.size());
-    EXPECT_EQ(4, statsLogEventWrapper.getWorkChains()[2].uids[0]);
-    EXPECT_EQ(5, statsLogEventWrapper.getWorkChains()[2].uids[1]);
-    EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[2].tags.size());
-    EXPECT_EQ("", statsLogEventWrapper.getWorkChains()[2].tags[0]);
-    EXPECT_EQ("", statsLogEventWrapper.getWorkChains()[2].tags[1]);
-
-    LogEvent event(statsLogEventWrapper, -1);
-    EXPECT_EQ(1, event.GetTagId());
-    EXPECT_EQ(1111L, event.GetElapsedTimestampNs());
-    EXPECT_EQ(2222L, event.GetLogdTimestampNs());
-    EXPECT_EQ(2, event.size());
-    EXPECT_EQ(6, event.getValues()[0].mValue.int_value);
-    EXPECT_EQ(10, event.getValues()[1].mValue.long_value);
-
-    LogEvent event1(statsLogEventWrapper, 0);
-
-    EXPECT_EQ(1, event1.GetTagId());
-    EXPECT_EQ(1111L, event1.GetElapsedTimestampNs());
-    EXPECT_EQ(2222L, event1.GetLogdTimestampNs());
-    EXPECT_EQ(6, event1.size());
-    EXPECT_EQ(1, event1.getValues()[0].mValue.int_value);
-    EXPECT_EQ(0x2010101, event1.getValues()[0].mField.getField());
-    EXPECT_EQ("tag1", event1.getValues()[1].mValue.str_value);
-    EXPECT_EQ(0x2010182, event1.getValues()[1].mField.getField());
-    EXPECT_EQ(2, event1.getValues()[2].mValue.int_value);
-    EXPECT_EQ(0x2010201, event1.getValues()[2].mField.getField());
-    EXPECT_EQ("tag2", event1.getValues()[3].mValue.str_value);
-    EXPECT_EQ(0x2018282, event1.getValues()[3].mField.getField());
-    EXPECT_EQ(6, event1.getValues()[4].mValue.int_value);
-    EXPECT_EQ(0x20000, event1.getValues()[4].mField.getField());
-    EXPECT_EQ(10, event1.getValues()[5].mValue.long_value);
-    EXPECT_EQ(0x30000, event1.getValues()[5].mField.getField());
-
-    LogEvent event2(statsLogEventWrapper, 1);
-
-    EXPECT_EQ(1, event2.GetTagId());
-    EXPECT_EQ(1111L, event2.GetElapsedTimestampNs());
-    EXPECT_EQ(2222L, event2.GetLogdTimestampNs());
-    EXPECT_EQ(4, event2.size());
-    EXPECT_EQ(3, event2.getValues()[0].mValue.int_value);
-    EXPECT_EQ(0x2010101, event2.getValues()[0].mField.getField());
-    EXPECT_EQ("tag3", event2.getValues()[1].mValue.str_value);
-    EXPECT_EQ(0x2018182, event2.getValues()[1].mField.getField());
-    EXPECT_EQ(6, event2.getValues()[2].mValue.int_value);
-    EXPECT_EQ(0x20000, event2.getValues()[2].mField.getField());
-    EXPECT_EQ(10, event2.getValues()[3].mValue.long_value);
-    EXPECT_EQ(0x30000, event2.getValues()[3].mField.getField());
-
-    LogEvent event3(statsLogEventWrapper, 2);
-
-    EXPECT_EQ(1, event3.GetTagId());
-    EXPECT_EQ(1111L, event3.GetElapsedTimestampNs());
-    EXPECT_EQ(2222L, event3.GetLogdTimestampNs());
-    EXPECT_EQ(6, event3.size());
-    EXPECT_EQ(4, event3.getValues()[0].mValue.int_value);
-    EXPECT_EQ(0x2010101, event3.getValues()[0].mField.getField());
-    EXPECT_EQ("", event3.getValues()[1].mValue.str_value);
-    EXPECT_EQ(0x2010182, event3.getValues()[1].mField.getField());
-    EXPECT_EQ(5, event3.getValues()[2].mValue.int_value);
-    EXPECT_EQ(0x2010201, event3.getValues()[2].mField.getField());
-    EXPECT_EQ("", event3.getValues()[3].mValue.str_value);
-    EXPECT_EQ(0x2018282, event3.getValues()[3].mField.getField());
-    EXPECT_EQ(6, event3.getValues()[4].mValue.int_value);
-    EXPECT_EQ(0x20000, event3.getValues()[4].mField.getField());
-    EXPECT_EQ(10, event3.getValues()[5].mValue.long_value);
-    EXPECT_EQ(0x30000, event3.getValues()[5].mField.getField());
+    const vector<FieldValue>& values = event.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_EQ(event.getUidFieldIndex(), 0);
 }
 
-TEST(LogEventTest, TestBinaryFieldAtom) {
-    Atom launcherAtom;
-    auto launcher_event = launcherAtom.mutable_launcher_event();
-    launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS);
-    launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW);
-    launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS);
+TEST(LogEventTest, TestAnnotationIdStateNested) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
 
-    auto extension = launcher_event->mutable_extension();
+    const vector<FieldValue>& values = event.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isNested());
+}
 
-    auto src_target = extension->add_src_target();
-    src_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE);
-    src_target->set_item(stats::launcher::LauncherTarget_Item_FOLDER_ICON);
+TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true);
 
-    auto dst_target = extension->add_dst_target();
-    dst_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE);
-    dst_target->set_item(stats::launcher::LauncherTarget_Item_WIDGET);
+    const vector<FieldValue>& values = event.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
+}
 
-    string extension_str;
-    extension->SerializeToString(&extension_str);
+TEST(LogEventTest, TestExclusiveStateAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true);
 
-    LogEvent event1(Atom::kLauncherEventFieldNumber, 1000);
+    const vector<FieldValue>& values = event.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
+}
 
-    event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS);
-    event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW);
-    event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS);
-    event1.write(extension_str);
-    event1.init();
+TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+    // Event has 10 ints and then an attribution chain
+    int numInts = 10;
+    int firstUidInChainIndex = numInts;
+    string tag1 = "tag1";
+    string tag2 = "tag2";
+    uint32_t uids[] = {1001, 1002};
+    const char* tags[] = {tag1.c_str(), tag2.c_str()};
 
-    ProtoOutputStream proto;
-    event1.ToProto(proto);
-
-    std::vector<uint8_t> outData;
-    outData.resize(proto.size());
-    size_t pos = 0;
-    sp<ProtoReader> reader = proto.data();
-    while (reader->readBuffer() != NULL) {
-        size_t toRead = reader->currentToRead();
-        std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
-        pos += toRead;
-        reader->move(toRead);
+    // Construct AStatsEvent
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 100);
+    for (int i = 0; i < numInts; i++) {
+        AStatsEvent_writeInt32(statsEvent, 10);
     }
+    AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_build(statsEvent);
 
-    std::string result_str(outData.begin(), outData.end());
-    std::string orig_str;
-    launcherAtom.SerializeToString(&orig_str);
+    // Construct LogEvent
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    AStatsEvent_release(statsEvent);
 
-    EXPECT_EQ(orig_str, result_str);
+    // Check annotation
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(values.size(), numInts + 4);
+    EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
 }
 
-TEST(LogEventTest, TestBinaryFieldAtom_empty) {
-    Atom launcherAtom;
-    auto launcher_event = launcherAtom.mutable_launcher_event();
-    launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS);
-    launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW);
-    launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS);
+TEST(LogEventTest, TestResetStateAnnotation) {
+    int32_t resetState = 10;
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState);
 
-    // empty string.
-    string extension_str;
-
-    LogEvent event1(Atom::kLauncherEventFieldNumber, 1000);
-
-    event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS);
-    event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW);
-    event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS);
-    event1.write(extension_str);
-    event1.init();
-
-    ProtoOutputStream proto;
-    event1.ToProto(proto);
-
-    std::vector<uint8_t> outData;
-    outData.resize(proto.size());
-    size_t pos = 0;
-    sp<ProtoReader> reader = proto.data();
-    while (reader->readBuffer() != NULL) {
-        size_t toRead = reader->currentToRead();
-        std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
-        pos += toRead;
-        reader->move(toRead);
-    }
-
-    std::string result_str(outData.begin(), outData.end());
-    std::string orig_str;
-    launcherAtom.SerializeToString(&orig_str);
-
-    EXPECT_EQ(orig_str, result_str);
+    const vector<FieldValue>& values = event.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_EQ(event.getResetState(), resetState);
 }
 
-TEST(LogEventTest, TestWriteExperimentIdsToProto) {
-    std::vector<int64_t> expIds;
-    expIds.push_back(5038);
-    std::vector<uint8_t> proto;
-
-    writeExperimentIdsToProto(expIds, &proto);
-
-    EXPECT_EQ(proto.size(), 3);
-    // Proto wire format for field ID 1, varint
-    EXPECT_EQ(proto[0], 0x08);
-    // varint of 5038, 2 bytes long
-    EXPECT_EQ(proto[1], 0xae);
-    EXPECT_EQ(proto[2], 0x27);
-}
-
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 71adc57..6259757 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -13,7 +13,15 @@
 // limitations under the License.
 
 #include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+#include <stdio.h>
 
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "metrics/metrics_test_helper.h"
 #include "src/condition/ConditionTracker.h"
 #include "src/matchers/LogMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
@@ -21,25 +29,26 @@
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"
+#include "src/state/StateManager.h"
 #include "statsd_test_util.h"
 
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-
-#include <stdio.h>
-#include <set>
-#include <unordered_map>
-#include <vector>
-
-using namespace android::os::statsd;
+using namespace testing;
 using android::sp;
+using android::os::statsd::Predicate;
+using std::map;
 using std::set;
 using std::unordered_map;
 using std::vector;
-using android::os::statsd::Predicate;
 
 #ifdef __ANDROID__
 
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
 const ConfigKey kConfigKey(0, 12345);
+const long kAlertId = 3;
 
 const long timeBaseSec = 1000;
 
@@ -85,7 +94,7 @@
     config.add_no_report_metric(3);
 
     auto alert = config.add_alert();
-    alert->set_id(3);
+    alert->set_id(kAlertId);
     alert->set_metric_id(3);
     alert->set_num_buckets(10);
     alert->set_refractory_period_secs(100);
@@ -218,7 +227,7 @@
     metric->mutable_dimensions_in_what()->add_child()->set_field(1);
 
     auto alert = config.add_alert();
-    alert->set_id(103);
+    alert->set_id(kAlertId);
     alert->set_metric_id(3);
     alert->set_num_buckets(10);
     alert->set_refractory_period_secs(100);
@@ -267,6 +276,157 @@
     return config;
 }
 
+StatsdConfig buildConfigWithDifferentPredicates() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    auto pulledAtomMatcher =
+            CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE);
+    *config.add_atom_matcher() = pulledAtomMatcher;
+    auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnAtomMatcher;
+    auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffAtomMatcher;
+    auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = batteryNoneAtomMatcher;
+    auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher();
+    *config.add_atom_matcher() = batteryUsbAtomMatcher;
+
+    // Simple condition with InitialValue set to default (unknown).
+    auto screenOnUnknownPredicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = screenOnUnknownPredicate;
+
+    // Simple condition with InitialValue set to false.
+    auto screenOnFalsePredicate = config.add_predicate();
+    screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse"));
+    SimplePredicate* simpleScreenOnFalsePredicate =
+            screenOnFalsePredicate->mutable_simple_predicate();
+    simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Simple condition with InitialValue set to false.
+    auto onBatteryFalsePredicate = config.add_predicate();
+    onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse"));
+    SimplePredicate* simpleOnBatteryFalsePredicate =
+            onBatteryFalsePredicate->mutable_simple_predicate();
+    simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Combination condition with both simple condition InitialValues set to false.
+    auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate();
+    screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse"));
+    screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(*screenOnFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+
+    // Combination condition with one simple condition InitialValue set to unknown and one set to
+    // false.
+    auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate();
+    screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse"));
+    screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOnUnknownPredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+
+    // Simple condition metric with initial value false.
+    ValueMetric* metric1 = config.add_value_metric();
+    metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse"));
+    metric1->set_what(pulledAtomMatcher.id());
+    *metric1->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric1->set_bucket(FIVE_MINUTES);
+    metric1->set_condition(screenOnFalsePredicate->id());
+
+    // Simple condition metric with initial value unknown.
+    ValueMetric* metric2 = config.add_value_metric();
+    metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown"));
+    metric2->set_what(pulledAtomMatcher.id());
+    *metric2->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric2->set_bucket(FIVE_MINUTES);
+    metric2->set_condition(screenOnUnknownPredicate.id());
+
+    // Combination condition metric with initial values false and false.
+    ValueMetric* metric3 = config.add_value_metric();
+    metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse"));
+    metric3->set_what(pulledAtomMatcher.id());
+    *metric3->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric3->set_bucket(FIVE_MINUTES);
+    metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id());
+
+    // Combination condition metric with initial values unknown and false.
+    ValueMetric* metric4 = config.add_value_metric();
+    metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse"));
+    metric4->set_what(pulledAtomMatcher.id());
+    *metric4->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric4->set_bucket(FIVE_MINUTES);
+    metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id());
+
+    return config;
+}
+
+bool isSubset(const set<int32_t>& set1, const set<int32_t>& set2) {
+    return std::includes(set2.begin(), set2.end(), set1.begin(), set1.end());
+}
+}  // anonymous namespace
+
+TEST(MetricsManagerTest, TestInitialConditions) {
+    UidMap uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildConfigWithDifferentPredicates();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_TRUE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers,
+            allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
+            trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap,
+            deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
+            noReportMetricIds));
+    ASSERT_EQ(4u, allMetricProducers.size());
+    ASSERT_EQ(5u, allConditionTrackers.size());
+
+    ConditionKey queryKey;
+    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
+
+    allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[1]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[2]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[3]);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]);
+
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition);
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition);
+}
+
 TEST(MetricsManagerTest, TestGoodConfig) {
     UidMap uidMap;
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -284,6 +444,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
@@ -293,10 +454,14 @@
                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                  trackerToMetricMap, trackerToConditionMap,
                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                 metricsWithActivation, noReportMetricIds));
-    EXPECT_EQ(1u, allMetricProducers.size());
-    EXPECT_EQ(1u, allAnomalyTrackers.size());
-    EXPECT_EQ(1u, noReportMetricIds.size());
+                                 alertTrackerMap, metricsWithActivation,
+                                 noReportMetricIds));
+    ASSERT_EQ(1u, allMetricProducers.size());
+    ASSERT_EQ(1u, allAnomalyTrackers.size());
+    ASSERT_EQ(1u, noReportMetricIds.size());
+    ASSERT_EQ(1u, alertTrackerMap.size());
+    EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end());
+    EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0);
 }
 
 TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
@@ -316,6 +481,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
@@ -325,7 +491,8 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -345,6 +512,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
@@ -354,7 +522,8 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -374,6 +543,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -382,7 +552,8 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingPredicate) {
@@ -402,6 +573,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -410,7 +582,7 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCirclePredicateDependency) {
@@ -430,6 +602,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
@@ -439,7 +612,8 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
@@ -459,6 +633,7 @@
     unordered_map<int, std::vector<int>> trackerToConditionMap;
     unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
     unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
     vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
@@ -468,9 +643,157 @@
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
                                   activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  metricsWithActivation, noReportMetricIds));
+                                  alertTrackerMap, metricsWithActivation,
+                                  noReportMetricIds));
 }
 
+TEST(MetricsManagerTest, TestLogSources) {
+    string app1 = "app1";
+    set<int32_t> app1Uids = {1111, 11111};
+    string app2 = "app2";
+    set<int32_t> app2Uids = {2222};
+    string app3 = "app3";
+    set<int32_t> app3Uids = {3333, 1111};
+
+    map<string, set<int32_t>> pkgToUids;
+    pkgToUids[app1] = app1Uids;
+    pkgToUids[app2] = app2Uids;
+    pkgToUids[app3] = app3Uids;
+
+    int32_t atom1 = 10;
+    int32_t atom2 = 20;
+    int32_t atom3 = 30;
+    sp<MockUidMap> uidMap = new StrictMock<MockUidMap>();
+    EXPECT_CALL(*uidMap, getAppUid(_))
+            .Times(4)
+            .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) {
+                const auto& it = pkgToUids.find(pkg);
+                if (it != pkgToUids.end()) {
+                    return it->second;
+                }
+                return set<int32_t>();
+            }));
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1);
+    EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1);
+
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodConfig();
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.add_allowed_log_source(app1);
+    config.add_default_pull_packages("AID_SYSTEM");
+    config.add_default_pull_packages("AID_ROOT");
+
+    const set<int32_t> defaultPullUids = {AID_SYSTEM, AID_ROOT};
+
+    PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages();
+    pullAtomPackages->set_atom_id(atom1);
+    pullAtomPackages->add_packages(app1);
+    pullAtomPackages->add_packages(app3);
+
+    pullAtomPackages = config.add_pull_atom_packages();
+    pullAtomPackages->set_atom_id(atom2);
+    pullAtomPackages->add_packages(app2);
+    pullAtomPackages->add_packages("AID_STATSD");
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    EXPECT_TRUE(metricsManager.isConfigValid());
+
+    ASSERT_EQ(metricsManager.mAllowedUid.size(), 1);
+    EXPECT_EQ(metricsManager.mAllowedUid[0], AID_SYSTEM);
+
+    ASSERT_EQ(metricsManager.mAllowedPkg.size(), 1);
+    EXPECT_EQ(metricsManager.mAllowedPkg[0], app1);
+
+    ASSERT_EQ(metricsManager.mAllowedLogSources.size(), 3);
+    EXPECT_TRUE(isSubset({AID_SYSTEM}, metricsManager.mAllowedLogSources));
+    EXPECT_TRUE(isSubset(app1Uids, metricsManager.mAllowedLogSources));
+
+    ASSERT_EQ(metricsManager.mDefaultPullUids.size(), 2);
+    EXPECT_TRUE(isSubset(defaultPullUids, metricsManager.mDefaultPullUids));
+    ;
+
+    vector<int32_t> atom1Uids = metricsManager.getPullAtomUids(atom1);
+    ASSERT_EQ(atom1Uids.size(), 5);
+    set<int32_t> expectedAtom1Uids;
+    expectedAtom1Uids.insert(defaultPullUids.begin(), defaultPullUids.end());
+    expectedAtom1Uids.insert(app1Uids.begin(), app1Uids.end());
+    expectedAtom1Uids.insert(app3Uids.begin(), app3Uids.end());
+    EXPECT_TRUE(isSubset(expectedAtom1Uids, set<int32_t>(atom1Uids.begin(), atom1Uids.end())));
+
+    vector<int32_t> atom2Uids = metricsManager.getPullAtomUids(atom2);
+    ASSERT_EQ(atom2Uids.size(), 4);
+    set<int32_t> expectedAtom2Uids;
+    expectedAtom1Uids.insert(defaultPullUids.begin(), defaultPullUids.end());
+    expectedAtom1Uids.insert(app2Uids.begin(), app2Uids.end());
+    expectedAtom1Uids.insert(AID_STATSD);
+    EXPECT_TRUE(isSubset(expectedAtom2Uids, set<int32_t>(atom2Uids.begin(), atom2Uids.end())));
+
+    vector<int32_t> atom3Uids = metricsManager.getPullAtomUids(atom3);
+    ASSERT_EQ(atom3Uids.size(), 2);
+    EXPECT_TRUE(isSubset(defaultPullUids, set<int32_t>(atom3Uids.begin(), atom3Uids.end())));
+}
+
+TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodConfig();
+    config.add_whitelisted_atom_ids(3);
+    config.add_whitelisted_atom_ids(4);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    LogEvent event(0 /* uid */, 0 /* pid */);
+    CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */);
+    EXPECT_FALSE(metricsManager.checkLogCredentials(event));
+
+    CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */);
+    EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+
+    CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */);
+    EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+}
+
+TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodConfig();
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.add_whitelisted_atom_ids(3);
+    config.add_whitelisted_atom_ids(4);
+
+    State state;
+    state.set_id(1);
+    state.set_atom_id(3);
+
+    *config.add_state() = state;
+
+    config.mutable_count_metric(0)->add_slice_by_state(state.id());
+
+    StateManager::getInstance().clear();
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_FALSE(metricsManager.isConfigValid());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index fe25a25..1e6680c 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -13,6 +13,11 @@
 // limitations under the License.
 
 #include "StatsLogProcessor.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
 #include "StatsService.h"
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
@@ -20,18 +25,14 @@
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "packages/UidMap.h"
+#include "statslog_statsdtest.h"
 #include "storage/StorageManager.h"
-#include "statslog.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
 #include "tests/statsd_test_util.h"
 
-#include <stdio.h>
-
 using namespace android;
 using namespace testing;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -49,10 +50,12 @@
     MockMetricsManager()
         : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(),
                          new StatsPullerManager(),
-                         new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
-                                          [](const sp<IStatsCompanionService>&) {}),
-                         new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
-                                          [](const sp<IStatsCompanionService>&) {})) {
+                         new AlarmMonitor(10,
+                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                          [](const shared_ptr<IStatsCompanionService>&) {}),
+                         new AlarmMonitor(10,
+                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                          [](const shared_ptr<IStatsCompanionService>&) {})) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
@@ -76,9 +79,9 @@
     // Expect only the first flush to trigger a check for byte size since the last two are
     // rate-limited.
     EXPECT_CALL(mockMetricsManager, byteSize()).Times(1);
-    p.flushIfNecessaryLocked(99, key, mockMetricsManager);
-    p.flushIfNecessaryLocked(100, key, mockMetricsManager);
-    p.flushIfNecessaryLocked(101, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
 }
 
 TEST(StatsLogProcessorTest, TestRateLimitBroadcast) {
@@ -103,7 +106,7 @@
                     StatsdStats::kMaxMetricsBytesPerConfig * .95)));
 
     // Expect only one broadcast despite always returning a size that should trigger broadcast.
-    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
     EXPECT_EQ(1, broadcastCount);
 
     // b/73089712
@@ -136,7 +139,7 @@
     EXPECT_CALL(mockMetricsManager, dropData(_)).Times(1);
 
     // Expect to call the onDumpReport and skip the broadcast.
-    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
     EXPECT_EQ(0, broadcastCount);
 }
 
@@ -183,7 +186,7 @@
     EXPECT_TRUE(output.reports_size() > 0);
     auto uidmap = output.reports(0).uid_map();
     EXPECT_TRUE(uidmap.snapshots_size() > 0);
-    EXPECT_EQ(2, uidmap.snapshots(0).package_info_size());
+    ASSERT_EQ(2, uidmap.snapshots(0).package_info_size());
 }
 
 TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) {
@@ -244,7 +247,7 @@
     output.ParseFromArray(bytes.data(), bytes.size());
     EXPECT_TRUE(output.reports_size() > 0);
     auto report = output.reports(0);
-    EXPECT_EQ(1, report.annotation_size());
+    ASSERT_EQ(1, report.annotation_size());
     EXPECT_EQ(1, report.annotation(0).field_int64());
     EXPECT_EQ(2, report.annotation(0).field_int32());
 }
@@ -252,7 +255,7 @@
 TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) {
     // Setup a simple config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = wakelockAcquireMatcher;
 
@@ -264,37 +267,97 @@
     ConfigKey cfgKey;
     sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey);
 
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2);
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event =
+            CreateAcquireWakelockEvent(2 /*timestamp*/, attributionUids, attributionTags, "wl1");
     processor->OnLogEvent(event.get());
 
     vector<uint8_t> bytes;
     ConfigMetricsReportList output;
 
     // Dump report WITHOUT erasing data.
-    processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST, &bytes);
+    processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST,
+                            &bytes);
     output.ParseFromArray(bytes.data(), bytes.size());
-    EXPECT_EQ(output.reports_size(), 1);
-    EXPECT_EQ(output.reports(0).metrics_size(), 1);
-    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+    ASSERT_EQ(output.reports_size(), 1);
+    ASSERT_EQ(output.reports(0).metrics_size(), 1);
+    ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
 
     // Dump report WITH erasing data. There should be data since we didn't previously erase it.
     processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes);
     output.ParseFromArray(bytes.data(), bytes.size());
-    EXPECT_EQ(output.reports_size(), 1);
-    EXPECT_EQ(output.reports(0).metrics_size(), 1);
-    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+    ASSERT_EQ(output.reports_size(), 1);
+    ASSERT_EQ(output.reports(0).metrics_size(), 1);
+    ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
 
     // Dump report again. There should be no data since we erased it.
     processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes);
     output.ParseFromArray(bytes.data(), bytes.size());
     // We don't care whether statsd has a report, as long as it has no count metrics in it.
-    bool noData = output.reports_size() == 0
-            || output.reports(0).metrics_size() == 0
-            || output.reports(0).metrics(0).count_metrics().data_size() == 0;
+    bool noData = output.reports_size() == 0 || output.reports(0).metrics_size() == 0 ||
+                  output.reports(0).metrics(0).count_metrics().data_size() == 0;
     EXPECT_TRUE(noData);
 }
 
+TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) {
+    // Setup simple config key corresponding to empty config.
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; });
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(false);
+    p.OnConfigUpdated(0, key, config);
+    EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+    config.add_default_pull_packages("AID_STATSD");
+    p.OnConfigUpdated(5, key, config);
+    EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+    p.OnConfigRemoved(key);
+    EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+}
+
+TEST(StatsLogProcessorTest, InvalidConfigRemoved) {
+    // Setup simple config key corresponding to empty config.
+    StatsdStats::getInstance().reset();
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+                 {String16("p1"), String16("p2")}, {String16(""), String16("")});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+                        [](const ConfigKey& key) { return true; },
+                        [](const int&, const vector<int64_t>&) {return true;});
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(true);
+    p.OnConfigUpdated(0, key, config);
+    EXPECT_EQ(1, p.mMetricsManagers.size());
+    EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end());
+    // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset.
+    EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(),
+              StatsdStats::getInstance().mConfigStats.find(key));
+    EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size());
+
+    StatsdConfig invalidConfig = MakeConfig(true);
+    invalidConfig.clear_allowed_log_source();
+    p.OnConfigUpdated(0, key, invalidConfig);
+    EXPECT_EQ(0, p.mMetricsManagers.size());
+    // The current configs should not contain the invalid config.
+    EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(),
+              StatsdStats::getInstance().mConfigStats.find(key));
+    // Both "config" and "invalidConfig" should be in the icebox.
+    EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size());
+
+}
+
+
 TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) {
     int uid = 1111;
 
@@ -392,15 +455,16 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            timeBase1, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
@@ -408,7 +472,7 @@
     processor.OnConfigUpdated(2, cfgKey2, config2);
     processor.OnConfigUpdated(3, cfgKey3, config3);
 
-    EXPECT_EQ(3, processor.mMetricsManagers.size());
+    ASSERT_EQ(3, processor.mMetricsManagers.size());
 
     // Expect the first config and both metrics in it to be active.
     auto it = processor.mMetricsManagers.find(cfgKey1);
@@ -492,8 +556,10 @@
     EXPECT_EQ(broadcastCount, 0);
 
     // Activate all 3 metrics that were not active.
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event =
+            CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1");
     processor.OnLogEvent(event.get());
 
     // Assert that all 3 configs are active.
@@ -503,23 +569,23 @@
 
     // A broadcast should have happened, and all 3 configs should be active in the broadcast.
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 3);
-    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1)
-            != activeConfigsBroadcast.end());
-    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2)
-            != activeConfigsBroadcast.end());
-    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3)
-            != activeConfigsBroadcast.end());
+    ASSERT_EQ(activeConfigsBroadcast.size(), 3);
+    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1) !=
+                activeConfigsBroadcast.end());
+    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2) !=
+                activeConfigsBroadcast.end());
+    EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3) !=
+                activeConfigsBroadcast.end());
 
     // When we shut down, metrics 3 & 5 have 100ns remaining, metric 6 has 100s + 100ns.
     int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
     processor.SaveActiveConfigsToDisk(shutDownTime);
     const int64_t ttl3 = event->GetElapsedTimestampNs() +
-            metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
+                         metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
     const int64_t ttl5 = event->GetElapsedTimestampNs() +
-            metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
+                         metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
     const int64_t ttl6 = event->GetElapsedTimestampNs() +
-            metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
+                         metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
 
     // Create a second StatsLogProcessor and push the same 3 configs.
     long timeBase2 = 1000;
@@ -528,7 +594,7 @@
     processor2->OnConfigUpdated(timeBase2, cfgKey2, config2);
     processor2->OnConfigUpdated(timeBase2, cfgKey3, config3);
 
-    EXPECT_EQ(3, processor2->mMetricsManagers.size());
+    ASSERT_EQ(3, processor2->mMetricsManagers.size());
 
     // First config and both metrics are active.
     it = processor2->mMetricsManagers.find(cfgKey1);
@@ -587,7 +653,7 @@
     EXPECT_TRUE(it != processor2->mMetricsManagers.end());
     auto& metricsManager1003 = it->second;
     EXPECT_FALSE(metricsManager1003->isActive());
-    EXPECT_EQ(2, metricsManager1003->mAllMetricProducers.size());
+    ASSERT_EQ(2, metricsManager1003->mAllMetricProducers.size());
 
     metricIt = metricsManager1003->mAllMetricProducers.begin();
     for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) {
@@ -670,7 +736,7 @@
     sp<StatsLogProcessor> processor =
             CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1);
 
-    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
     auto it = processor->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor->mMetricsManagers.end());
     auto& metricsManager1 = it->second;
@@ -701,8 +767,10 @@
     EXPECT_EQ(0, activation1->start_ns);
     EXPECT_EQ(kNotActive, activation1->state);
 
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event =
+            CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1");
     processor->OnLogEvent(event.get());
 
     EXPECT_FALSE(metricProducer1->isActive());
@@ -718,7 +786,7 @@
     sp<StatsLogProcessor> processor2 =
             CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1);
 
-    EXPECT_EQ(1, processor2->mMetricsManagers.size());
+    ASSERT_EQ(1, processor2->mMetricsManagers.size());
     it = processor2->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor2->mMetricsManagers.end());
     auto& metricsManager1001 = it->second;
@@ -801,7 +869,7 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
     auto it = processor->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor->mMetricsManagers.end());
     auto& metricsManager1 = it->second;
@@ -830,7 +898,7 @@
     int i = 0;
     for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
         if (metricsManager1->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
+            metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
     }
@@ -842,7 +910,7 @@
     i = 0;
     for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
         if (metricsManager1->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
+            metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
     }
@@ -853,8 +921,10 @@
     // }}}------------------------------------------------------------------------------
 
     // Trigger Activation 1 for Metric 1
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event =
+            CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1");
     processor->OnLogEvent(event.get());
 
     // Metric 1 is not active; Activation 1 set to kActiveOnBoot
@@ -884,7 +954,7 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor2->mMetricsManagers.size());
+    ASSERT_EQ(1, processor2->mMetricsManagers.size());
     it = processor2->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor2->mMetricsManagers.end());
     auto& metricsManager1001 = it->second;
@@ -913,7 +983,7 @@
     i = 0;
     for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
         if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
+            metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
     }
@@ -925,7 +995,7 @@
     i = 0;
     for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
         if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
+            metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
     }
@@ -952,10 +1022,8 @@
     // }}}--------------------------------------------------------------------------------
 
     // Trigger Activation 2 for Metric 1.
-    auto screenOnEvent = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_ON,
-            timeBase2 + 200
-    );
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON);
     processor2->OnLogEvent(screenOnEvent.get());
 
     // Metric 1 active; Activation 1 is active, Activation 2 is set to kActiveOnBoot
@@ -987,7 +1055,7 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor3->mMetricsManagers.size());
+    ASSERT_EQ(1, processor3->mMetricsManagers.size());
     it = processor3->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor3->mMetricsManagers.end());
     auto& metricsManagerTimeBase3 = it->second;
@@ -1016,7 +1084,7 @@
     i = 0;
     for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
         if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
+            metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
     }
@@ -1028,7 +1096,7 @@
     i = 0;
     for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
         if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
+            metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
     }
@@ -1057,10 +1125,8 @@
     // }}}-------------------------------------------------------------------------------
 
     // Trigger Activation 2 for Metric 1 again.
-    screenOnEvent = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_ON,
-            timeBase3 + 100 * NS_PER_SEC
-    );
+    screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC,
+                                                  android::view::DISPLAY_STATE_ON);
     processor3->OnLogEvent(screenOnEvent.get());
 
     // Metric 1 active; Activation 1 is not active, Activation 2 is set to active
@@ -1091,7 +1157,7 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor4->mMetricsManagers.size());
+    ASSERT_EQ(1, processor4->mMetricsManagers.size());
     it = processor4->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor4->mMetricsManagers.end());
     auto& metricsManagerTimeBase4 = it->second;
@@ -1120,7 +1186,7 @@
     i = 0;
     for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
         if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
+            metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
     }
@@ -1132,7 +1198,7 @@
     i = 0;
     for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
         if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
+            metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
     }
@@ -1199,87 +1265,68 @@
 
     ConfigKey cfgKey1(uid, 12341);
     long timeBase1 = 1;
-    sp<StatsLogProcessor> processor =
+    sp<StatsLogProcessor> processor1 =
             CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1);
 
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor->mMetricsManagers.size());
-    auto it = processor->mMetricsManagers.find(cfgKey1);
-    EXPECT_TRUE(it != processor->mMetricsManagers.end());
+    ASSERT_EQ(1, processor1->mMetricsManagers.size());
+    auto it = processor1->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor1->mMetricsManagers.end());
     auto& metricsManager1 = it->second;
     EXPECT_TRUE(metricsManager1->isActive());
 
-    auto metricIt = metricsManager1->mAllMetricProducers.begin();
-    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId1) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
-    auto& metricProducer1 = *metricIt;
-    EXPECT_FALSE(metricProducer1->isActive());
+    ASSERT_EQ(metricsManager1->mAllMetricProducers.size(), 2);
+    // We assume that the index of a MetricProducer within the mAllMetricProducers
+    // array follows the order in which metrics are added to the config.
+    auto& metricProducer1_1 = metricsManager1->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer1_1->getMetricId(), metricId1);
+    EXPECT_FALSE(metricProducer1_1->isActive());  // inactive due to associated MetricActivation
 
-    metricIt = metricsManager1->mAllMetricProducers.begin();
-    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId2) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
-    auto& metricProducer2 = *metricIt;
-    EXPECT_TRUE(metricProducer2->isActive());
+    auto& metricProducer1_2 = metricsManager1->mAllMetricProducers[1];
+    EXPECT_EQ(metricProducer1_2->getMetricId(), metricId2);
+    EXPECT_TRUE(metricProducer1_2->isActive());
 
-    int i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
-            break;
-        }
-    }
-    const auto& activation1 = metricProducer1->mEventActivationMap.at(i);
-    EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
-    EXPECT_EQ(0, activation1->start_ns);
-    EXPECT_EQ(kNotActive, activation1->state);
-    EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType);
+    ASSERT_EQ(metricProducer1_1->mEventActivationMap.size(), 2);
+    // The key in mEventActivationMap is the index of the associated atom matcher. We assume
+    // that matchers are indexed in the order that they are added to the config.
+    const auto& activation1_1_1 = metricProducer1_1->mEventActivationMap.at(0);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1_1_1->ttl_ns);
+    EXPECT_EQ(0, activation1_1_1->start_ns);
+    EXPECT_EQ(kNotActive, activation1_1_1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activation1_1_1->activationType);
 
-    i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
-            break;
-        }
-    }
-    const auto& activation2 = metricProducer1->mEventActivationMap.at(i);
-    EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns);
-    EXPECT_EQ(0, activation2->start_ns);
-    EXPECT_EQ(kNotActive, activation2->state);
-    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType);
+    const auto& activation1_1_2 = metricProducer1_1->mEventActivationMap.at(1);
+    EXPECT_EQ(200 * NS_PER_SEC, activation1_1_2->ttl_ns);
+    EXPECT_EQ(0, activation1_1_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1_1_2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1_1_2->activationType);
     // }}}------------------------------------------------------------------------------
 
     // Trigger Activation 1 for Metric 1
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
-    processor->OnLogEvent(event.get());
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event =
+            CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1");
+    processor1->OnLogEvent(event.get());
 
     // Metric 1 is not active; Activation 1 set to kActiveOnBoot
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_FALSE(metricProducer1->isActive());
-    EXPECT_EQ(0, activation1->start_ns);
-    EXPECT_EQ(kActiveOnBoot, activation1->state);
-    EXPECT_EQ(0, activation2->start_ns);
-    EXPECT_EQ(kNotActive, activation2->state);
+    EXPECT_FALSE(metricProducer1_1->isActive());
+    EXPECT_EQ(0, activation1_1_1->start_ns);
+    EXPECT_EQ(kActiveOnBoot, activation1_1_1->state);
+    EXPECT_EQ(0, activation1_1_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1_1_2->state);
 
-    EXPECT_TRUE(metricProducer2->isActive());
+    EXPECT_TRUE(metricProducer1_2->isActive());
     // }}}-----------------------------------------------------------------------------
 
     // Simulate shutdown by saving state to disk
     int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
-    processor->SaveActiveConfigsToDisk(shutDownTime);
-    EXPECT_FALSE(metricProducer1->isActive());
-    int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC;
+    processor1->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_FALSE(metricProducer1_1->isActive());
 
     // Simulate device restarted state by creating new instance of StatsLogProcessor with the
     // same config.
@@ -1290,58 +1337,37 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor2->mMetricsManagers.size());
+    ASSERT_EQ(1, processor2->mMetricsManagers.size());
     it = processor2->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor2->mMetricsManagers.end());
-    auto& metricsManager1001 = it->second;
-    EXPECT_TRUE(metricsManager1001->isActive());
+    auto& metricsManager2 = it->second;
+    EXPECT_TRUE(metricsManager2->isActive());
 
-    metricIt = metricsManager1001->mAllMetricProducers.begin();
-    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId1) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
-    auto& metricProducer1001 = *metricIt;
-    EXPECT_FALSE(metricProducer1001->isActive());
+    ASSERT_EQ(metricsManager2->mAllMetricProducers.size(), 2);
+    // We assume that the index of a MetricProducer within the mAllMetricProducers
+    // array follows the order in which metrics are added to the config.
+    auto& metricProducer2_1 = metricsManager2->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer2_1->getMetricId(), metricId1);
+    EXPECT_FALSE(metricProducer2_1->isActive());
 
-    metricIt = metricsManager1001->mAllMetricProducers.begin();
-    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId2) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
-    auto& metricProducer1002 = *metricIt;
-    EXPECT_TRUE(metricProducer1002->isActive());
+    auto& metricProducer2_2 = metricsManager2->mAllMetricProducers[1];
+    EXPECT_EQ(metricProducer2_2->getMetricId(), metricId2);
+    EXPECT_TRUE(metricProducer2_2->isActive());
 
-    i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
-            break;
-        }
-    }
-    const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i);
-    EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns);
-    EXPECT_EQ(0, activation1001_1->start_ns);
-    EXPECT_EQ(kNotActive, activation1001_1->state);
-    EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001_1->activationType);
+    ASSERT_EQ(metricProducer2_1->mEventActivationMap.size(), 2);
+    // The key in mEventActivationMap is the index of the associated atom matcher. We assume
+    // that matchers are indexed in the order that they are added to the config.
+    const auto& activation2_1_1 = metricProducer2_1->mEventActivationMap.at(0);
+    EXPECT_EQ(100 * NS_PER_SEC, activation2_1_1->ttl_ns);
+    EXPECT_EQ(0, activation2_1_1->start_ns);
+    EXPECT_EQ(kNotActive, activation2_1_1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activation2_1_1->activationType);
 
-    i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
-            break;
-        }
-    }
-
-    const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i);
-    EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns);
-    EXPECT_EQ(0, activation1001_2->start_ns);
-    EXPECT_EQ(kNotActive, activation1001_2->state);
-    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1001_2->activationType);
+    const auto& activation2_1_2 = metricProducer2_1->mEventActivationMap.at(1);
+    EXPECT_EQ(200 * NS_PER_SEC, activation2_1_2->ttl_ns);
+    EXPECT_EQ(0, activation2_1_2->start_ns);
+    EXPECT_EQ(kNotActive, activation2_1_2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2_1_2->activationType);
     // }}}-----------------------------------------------------------------------------------
 
     // Load saved state from disk.
@@ -1350,42 +1376,41 @@
     // Metric 1 active; Activation 1 is active, Activation 2 is not active
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_TRUE(metricProducer1001->isActive());
-    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
-    EXPECT_EQ(kActive, activation1001_1->state);
-    EXPECT_EQ(0, activation1001_2->start_ns);
-    EXPECT_EQ(kNotActive, activation1001_2->state);
+    EXPECT_TRUE(metricProducer2_1->isActive());
+    int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC;
+    EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns);
+    EXPECT_EQ(kActive, activation2_1_1->state);
+    EXPECT_EQ(0, activation2_1_2->start_ns);
+    EXPECT_EQ(kNotActive, activation2_1_2->state);
 
-    EXPECT_TRUE(metricProducer1002->isActive());
+    EXPECT_TRUE(metricProducer2_2->isActive());
     // }}}--------------------------------------------------------------------------------
 
     // Trigger Activation 2 for Metric 1.
-    auto screenOnEvent = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_ON,
-            timeBase2 + 200
-    );
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON);
     processor2->OnLogEvent(screenOnEvent.get());
 
     // Metric 1 active; Activation 1 is active, Activation 2 is active
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_TRUE(metricProducer1001->isActive());
-    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
-    EXPECT_EQ(kActive, activation1001_1->state);
-    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation1001_2->start_ns);
-    EXPECT_EQ(kActive, activation1001_2->state);
+    EXPECT_TRUE(metricProducer2_1->isActive());
+    EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns);
+    EXPECT_EQ(kActive, activation2_1_1->state);
+    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation2_1_2->start_ns);
+    EXPECT_EQ(kActive, activation2_1_2->state);
 
-    EXPECT_TRUE(metricProducer1002->isActive());
+    EXPECT_TRUE(metricProducer2_2->isActive());
     // }}}---------------------------------------------------------------------------
 
     // Simulate shutdown by saving state to disk
     shutDownTime = timeBase2 + 50 * NS_PER_SEC;
     processor2->SaveActiveConfigsToDisk(shutDownTime);
-    EXPECT_TRUE(metricProducer1001->isActive());
-    EXPECT_TRUE(metricProducer1002->isActive());
-    ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime;
-    int64_t ttl2 = screenOnEvent->GetElapsedTimestampNs() +
-            metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime;
+    EXPECT_TRUE(metricProducer2_1->isActive());
+    EXPECT_TRUE(metricProducer2_2->isActive());
+    ttl1 -= shutDownTime - timeBase2;
+    int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC -
+                   (shutDownTime - screenOnEvent->GetElapsedTimestampNs());
 
     // Simulate device restarted state by creating new instance of StatsLogProcessor with the
     // same config.
@@ -1396,58 +1421,37 @@
     // Metric 1 is not active.
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor3->mMetricsManagers.size());
+    ASSERT_EQ(1, processor3->mMetricsManagers.size());
     it = processor3->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor3->mMetricsManagers.end());
-    auto& metricsManagerTimeBase3 = it->second;
-    EXPECT_TRUE(metricsManagerTimeBase3->isActive());
+    auto& metricsManager3 = it->second;
+    EXPECT_TRUE(metricsManager3->isActive());
 
-    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
-    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId1) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
-    auto& metricProducerTimeBase3_1 = *metricIt;
-    EXPECT_FALSE(metricProducerTimeBase3_1->isActive());
+    ASSERT_EQ(metricsManager3->mAllMetricProducers.size(), 2);
+    // We assume that the index of a MetricProducer within the mAllMetricProducers
+    // array follows the order in which metrics are added to the config.
+    auto& metricProducer3_1 = metricsManager3->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer3_1->getMetricId(), metricId1);
+    EXPECT_FALSE(metricProducer3_1->isActive());
 
-    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
-    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
-        if ((*metricIt)->getMetricId() == metricId2) {
-            break;
-        }
-    }
-    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
-    auto& metricProducerTimeBase3_2 = *metricIt;
-    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    auto& metricProducer3_2 = metricsManager3->mAllMetricProducers[1];
+    EXPECT_EQ(metricProducer3_2->getMetricId(), metricId2);
+    EXPECT_TRUE(metricProducer3_2->isActive());
 
-    i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger1->atom_matcher_id()) {
-            break;
-        }
-    }
-    const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
-    EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns);
-    EXPECT_EQ(0, activationTimeBase3_1->start_ns);
-    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
-    EXPECT_EQ(ACTIVATE_ON_BOOT, activationTimeBase3_1->activationType);
+    ASSERT_EQ(metricProducer3_1->mEventActivationMap.size(), 2);
+    // The key in mEventActivationMap is the index of the associated atom matcher. We assume
+    // that matchers are indexed in the order that they are added to the config.
+    const auto& activation3_1_1 = metricProducer3_1->mEventActivationMap.at(0);
+    EXPECT_EQ(100 * NS_PER_SEC, activation3_1_1->ttl_ns);
+    EXPECT_EQ(0, activation3_1_1->start_ns);
+    EXPECT_EQ(kNotActive, activation3_1_1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activation3_1_1->activationType);
 
-    i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
-                metric1ActivationTrigger2->atom_matcher_id()) {
-            break;
-        }
-    }
-
-    const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
-    EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns);
-    EXPECT_EQ(0, activationTimeBase3_2->start_ns);
-    EXPECT_EQ(kNotActive, activationTimeBase3_2->state);
-    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activationTimeBase3_2->activationType);
+    const auto& activation3_1_2 = metricProducer3_1->mEventActivationMap.at(1);
+    EXPECT_EQ(200 * NS_PER_SEC, activation3_1_2->ttl_ns);
+    EXPECT_EQ(0, activation3_1_2->start_ns);
+    EXPECT_EQ(kNotActive, activation3_1_2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation3_1_2->activationType);
     // }}}----------------------------------------------------------------------------------
 
     // Load saved state from disk.
@@ -1456,32 +1460,30 @@
     // Metric 1 active: Activation 1 is active, Activation 2 is active
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
-    EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns);
-    EXPECT_EQ(kActive, activationTimeBase3_1->state);
-    EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns);
-    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+    EXPECT_TRUE(metricProducer3_1->isActive());
+    EXPECT_EQ(timeBase3 + ttl1 - activation3_1_1->ttl_ns, activation3_1_1->start_ns);
+    EXPECT_EQ(kActive, activation3_1_1->state);
+    EXPECT_EQ(timeBase3 + ttl2 - activation3_1_2->ttl_ns, activation3_1_2->start_ns);
+    EXPECT_EQ(kActive, activation3_1_2->state);
 
-    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    EXPECT_TRUE(metricProducer3_2->isActive());
     // }}}-------------------------------------------------------------------------------
 
-
     // Trigger Activation 2 for Metric 1 again.
-    screenOnEvent = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_ON,
-            timeBase3 + 100 * NS_PER_SEC
-    );
+    screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC,
+                                                  android::view::DISPLAY_STATE_ON);
     processor3->OnLogEvent(screenOnEvent.get());
 
-    // Metric 1 active; Activation 1 is not active, Activation 2 is set to active
+    // Metric 1 active; Activation 1 is inactive (above screenOnEvent causes ttl1 to expire),
+    //                  Activation 2 is set to active
     // Metric 2 is active.
     // {{{---------------------------------------------------------------------------
-    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
-    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
-    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activationTimeBase3_2->start_ns);
-    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+    EXPECT_TRUE(metricProducer3_1->isActive());
+    EXPECT_EQ(kNotActive, activation3_1_1->state);
+    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation3_1_2->start_ns);
+    EXPECT_EQ(kActive, activation3_1_2->state);
 
-    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    EXPECT_TRUE(metricProducer3_2->isActive());
     // }}}---------------------------------------------------------------------------
 }
 
@@ -1549,9 +1551,9 @@
     metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY);
 
     // Send the config.
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = config1.SerializeAsString();
-    service.addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
+    service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
 
     // Make sure the config is stored on disk. Otherwise, we will not reset on system server death.
     StatsdConfig tmpConfig;
@@ -1562,13 +1564,13 @@
     // Metric 2 is not active.
     // Metric 3 is active.
     // {{{---------------------------------------------------------------------------
-    sp<StatsLogProcessor> processor = service.mProcessor;
-    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    sp<StatsLogProcessor> processor = service->mProcessor;
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
     auto it = processor->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor->mMetricsManagers.end());
     auto& metricsManager1 = it->second;
     EXPECT_TRUE(metricsManager1->isActive());
-    EXPECT_EQ(3, metricsManager1->mAllMetricProducers.size());
+    ASSERT_EQ(3, metricsManager1->mAllMetricProducers.size());
 
     auto& metricProducer1 = metricsManager1->mAllMetricProducers[0];
     EXPECT_EQ(metricId1, metricProducer1->getMetricId());
@@ -1583,7 +1585,7 @@
     EXPECT_TRUE(metricProducer3->isActive());
 
     // Check event activations.
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers.size(), 4);
+    ASSERT_EQ(metricsManager1->mAllAtomMatchers.size(), 4);
     EXPECT_EQ(metricsManager1->mAllAtomMatchers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1 = metricProducer1->mEventActivationMap.at(0);
@@ -1619,13 +1621,16 @@
 
     // Trigger Activation 1 for Metric 1. Should activate on boot.
     // Trigger Activation 4 for Metric 2. Should activate immediately.
-    long configAddedTimeNs = metricsManager1->mLastReportTimeNs;
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 1 + configAddedTimeNs);
-    processor->OnLogEvent(event.get());
+    int64_t configAddedTimeNs = metricsManager1->mLastReportTimeNs;
+    std::vector<int> attributionUids = {111};
+    std::vector<string> attributionTags = {"App1"};
+    std::unique_ptr<LogEvent> event1 = CreateAcquireWakelockEvent(
+            1 + configAddedTimeNs, attributionUids, attributionTags, "wl1");
+    processor->OnLogEvent(event1.get());
 
-    event = CreateFinishScheduledJobEvent(attributions1, "finish1", 2 + configAddedTimeNs);
-    processor->OnLogEvent(event.get());
+    std::unique_ptr<LogEvent> event2 = CreateFinishScheduledJobEvent(
+            2 + configAddedTimeNs, attributionUids, attributionTags, "finish1");
+    processor->OnLogEvent(event2.get());
 
     // Metric 1 is not active; Activation 1 set to kActiveOnBoot
     // Metric 2 is active. Activation 4 set to kActive
@@ -1653,16 +1658,16 @@
     EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs);
 
     // System server dies.
-    service.binderDied(nullptr);
+    service->statsCompanionServiceDiedImpl();
 
     // We should have a new metrics manager. Lets get it and ensure activation status is restored.
     // {{{---------------------------------------------------------------------------
-    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
     it = processor->mMetricsManagers.find(cfgKey1);
     EXPECT_TRUE(it != processor->mMetricsManagers.end());
     auto& metricsManager2 = it->second;
     EXPECT_TRUE(metricsManager2->isActive());
-    EXPECT_EQ(3, metricsManager2->mAllMetricProducers.size());
+    ASSERT_EQ(3, metricsManager2->mAllMetricProducers.size());
 
     auto& metricProducer1001 = metricsManager2->mAllMetricProducers[0];
     EXPECT_EQ(metricId1, metricProducer1001->getMetricId());
@@ -1680,7 +1685,7 @@
     // Activation 1 is kActiveOnBoot.
     // Activation 2 and 3 are not active.
     // Activation 4 is active.
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers.size(), 4);
+    ASSERT_EQ(metricsManager2->mAllAtomMatchers.size(), 4);
     EXPECT_EQ(metricsManager2->mAllAtomMatchers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0);
@@ -1713,6 +1718,163 @@
     EXPECT_EQ(kActive, activation1004->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType);
     // }}}------------------------------------------------------------------------------
+
+    // Clear the data stored on disk as a result of the system server death.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true, ADB_DUMP, FAST,
+                            &buffer);
+}
+
+TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    uint64_t eventTimeNs = 12355;
+    int atomId = 89;
+    int field1 = 90;
+    int field2 = 28;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey cfgKey;
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap);
+
+    shared_ptr<LogEvent> logEvent = makeUidLogEvent(atomId, eventTimeNs, hostUid, field1, field2);
+
+    processor->OnLogEvent(logEvent.get());
+
+    const vector<FieldValue>* actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value);
+}
+
+TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUid) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    uint64_t eventTimeNs = 12355;
+    int atomId = 89;
+    int field1 = 90;
+    int field2 = 28;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey cfgKey;
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap);
+
+    shared_ptr<LogEvent> logEvent =
+            makeUidLogEvent(atomId, eventTimeNs, isolatedUid, field1, field2);
+
+    processor->OnLogEvent(logEvent.get());
+
+    const vector<FieldValue>* actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value);
+}
+
+TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUidAttributionChain) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    uint64_t eventTimeNs = 12355;
+    int atomId = 89;
+    int field1 = 90;
+    int field2 = 28;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey cfgKey;
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap);
+
+    shared_ptr<LogEvent> logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {hostUid, 200},
+                                                            {"tag1", "tag2"}, field1, field2);
+
+    processor->OnLogEvent(logEvent.get());
+
+    const vector<FieldValue>* actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionChain) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    uint64_t eventTimeNs = 12355;
+    int atomId = 89;
+    int field1 = 90;
+    int field2 = 28;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey cfgKey;
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap);
+
+    shared_ptr<LogEvent> logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {isolatedUid, 200},
+                                                            {"tag1", "tag2"}, field1, field2);
+
+    processor->OnLogEvent(logEvent.get());
+
+    const vector<FieldValue>* actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey key(3, 4);
+
+    // TODO: All tests should not persist state on disk. This removes any reports that were present.
+    ProtoOutputStream proto;
+    StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false);
+
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap);
+    vector<uint8_t> bytes;
+
+    int64_t dumpTime1Ns = 1 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */,
+            true /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    ConfigMetricsReportList output;
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns);
+
+    int64_t dumpTime2Ns = 5 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */,
+            false /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    // Check that the dump report without clearing data is successful.
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns);
+    EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
+
+    int64_t dumpTime3Ns = 10 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */,
+            true /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    // Check that the previous dump report that didn't clear data did not overwrite the first dump's
+    // timestamps.
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns);
+    EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
+
 }
 
 #else
diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp
index 7c00531..cc38c4a 100644
--- a/cmds/statsd/tests/StatsService_test.cpp
+++ b/cmds/statsd/tests/StatsService_test.cpp
@@ -16,6 +16,7 @@
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
+#include <android/binder_interface_utils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -29,33 +30,34 @@
 namespace statsd {
 
 using android::util::ProtoOutputStream;
+using ::ndk::SharedRefBase;
 
 #ifdef __ANDROID__
 
 TEST(StatsServiceTest, TestAddConfig_simple) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     StatsdConfig config;
     config.set_id(12345);
     string serialized = config.SerializeAsString();
 
     EXPECT_TRUE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestAddConfig_empty) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = "";
 
     EXPECT_TRUE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestAddConfig_invalid) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     string serialized = "Invalid config!";
 
     EXPECT_FALSE(
-            service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+            service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
 }
 
 TEST(StatsServiceTest, TestGetUidFromArgs) {
@@ -63,38 +65,34 @@
     args.push(String8("-1"));
     args.push(String8("0"));
     args.push(String8("1"));
-    args.push(String8("9999999999999999999999999999999999"));
     args.push(String8("a1"));
     args.push(String8(""));
 
     int32_t uid;
 
-    StatsService service(nullptr, nullptr);
-    service.mEngBuild = true;
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mEngBuild = true;
 
     // "-1"
-    EXPECT_FALSE(service.getUidFromArgs(args, 0, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 0, uid));
 
     // "0"
-    EXPECT_TRUE(service.getUidFromArgs(args, 1, uid));
+    EXPECT_TRUE(service->getUidFromArgs(args, 1, uid));
     EXPECT_EQ(0, uid);
 
     // "1"
-    EXPECT_TRUE(service.getUidFromArgs(args, 2, uid));
+    EXPECT_TRUE(service->getUidFromArgs(args, 2, uid));
     EXPECT_EQ(1, uid);
 
-    // "999999999999999999"
-    EXPECT_FALSE(service.getUidFromArgs(args, 3, uid));
-
     // "a1"
-    EXPECT_FALSE(service.getUidFromArgs(args, 4, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 3, uid));
 
     // ""
-    EXPECT_FALSE(service.getUidFromArgs(args, 5, uid));
+    EXPECT_FALSE(service->getUidFromArgs(args, 4, uid));
 
     // For a non-userdebug, uid "1" cannot be impersonated.
-    service.mEngBuild = false;
-    EXPECT_FALSE(service.getUidFromArgs(args, 2, uid));
+    service->mEngBuild = false;
+    EXPECT_FALSE(service->getUidFromArgs(args, 2, uid));
 }
 
 #else
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index d9fa4e9..293e8ed 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -18,7 +18,7 @@
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "hash.h"
-#include "statslog.h"
+#include "statslog_statsdtest.h"
 #include "statsd_test_util.h"
 
 #include <android/util/ProtoOutputStream.h>
@@ -45,26 +45,20 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     // Construct the processor with a dummy sendBroadcast function that does nothing.
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [](const ConfigKey& key) { return true; },
-                        [](const int&, const vector<int64_t>&) {return true;});
-    LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
-    addEvent.write(100);  // parent UID
-    addEvent.write(101);  // isolated UID
-    addEvent.write(1);    // Indicates creation.
-    addEvent.init();
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; });
 
+    std::unique_ptr<LogEvent> addEvent = CreateIsolatedUidChangedEvent(
+            1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/);
     EXPECT_EQ(101, m->getHostUidOrSelf(101));
-
-    p.OnLogEvent(&addEvent);
+    p.OnLogEvent(addEvent.get());
     EXPECT_EQ(100, m->getHostUidOrSelf(101));
 
-    LogEvent removeEvent(android::util::ISOLATED_UID_CHANGED, 1);
-    removeEvent.write(100);  // parent UID
-    removeEvent.write(101);  // isolated UID
-    removeEvent.write(0);    // Indicates removal.
-    removeEvent.init();
-    p.OnLogEvent(&removeEvent);
+    std::unique_ptr<LogEvent> removeEvent = CreateIsolatedUidChangedEvent(
+            1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 0 /*is_create*/);
+    p.OnLogEvent(removeEvent.get());
     EXPECT_EQ(101, m->getHostUidOrSelf(101));
 }
 
@@ -92,7 +86,7 @@
     EXPECT_FALSE(m.hasApp(1000, "not.app"));
 
     std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 2u);
+    ASSERT_EQ(name_set.size(), 2u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
@@ -121,7 +115,7 @@
     m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
     std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 2u);
+    ASSERT_EQ(name_set.size(), 2u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
@@ -130,7 +124,7 @@
     EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
 
     name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 2u);
+    ASSERT_EQ(name_set.size(), 2u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
@@ -138,7 +132,7 @@
     EXPECT_FALSE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
     name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 1u);
+    ASSERT_EQ(name_set.size(), 1u);
     EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
@@ -155,14 +149,14 @@
     m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")},
                 {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")});
     std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 2u);
+    ASSERT_EQ(name_set.size(), 2u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
     // Adds a new name for uid 1000.
     m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16(""));
     name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 3u);
+    ASSERT_EQ(name_set.size(), 3u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
     EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
@@ -171,7 +165,7 @@
     // This name is also reused by another uid 2000.
     m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16(""));
     name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
-    EXPECT_EQ(name_set.size(), 1u);
+    ASSERT_EQ(name_set.size(), 1u);
     EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
     EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
 }
@@ -218,7 +212,7 @@
     // Check there's still a uidmap attached this one.
     UidMapping results;
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(1, results.snapshots_size());
     EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string());
 }
 
@@ -246,7 +240,7 @@
     // Snapshot should still contain this item as deleted.
     UidMapping results;
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots(0).package_info_size());
+    ASSERT_EQ(1, results.snapshots(0).package_info_size());
     EXPECT_EQ(true, results.snapshots(0).package_info(0).deleted());
 }
 
@@ -275,7 +269,7 @@
     ProtoOutputStream proto;
     m.appendUidMap(3, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size());
+    ASSERT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size());
 
     // Now remove all the apps.
     m.updateMap(1, uids, versions, versionStrings, apps, installers);
@@ -287,7 +281,7 @@
     m.appendUidMap(5, config1, nullptr, true, true, &proto);
     // Snapshot drops the first nine items.
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(maxDeletedApps, results.snapshots(0).package_info_size());
+    ASSERT_EQ(maxDeletedApps, results.snapshots(0).package_info_size());
 }
 
 TEST(UidMapTest, TestClearingOutput) {
@@ -319,44 +313,44 @@
     m.appendUidMap(2, config1, nullptr, true, true, &proto);
     UidMapping results;
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(1, results.snapshots_size());
 
     // We have to keep at least one snapshot in memory at all times.
     proto.clear();
     m.appendUidMap(2, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(1, results.snapshots_size());
 
     // Now add another configuration.
     m.OnConfigUpdated(config2);
     m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
-    EXPECT_EQ(1U, m.mChanges.size());
+    ASSERT_EQ(1U, m.mChanges.size());
     proto.clear();
     m.appendUidMap(6, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
-    EXPECT_EQ(1, results.changes_size());
-    EXPECT_EQ(1U, m.mChanges.size());
+    ASSERT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(1, results.changes_size());
+    ASSERT_EQ(1U, m.mChanges.size());
 
     // Add another delta update.
     m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16(""));
-    EXPECT_EQ(2U, m.mChanges.size());
+    ASSERT_EQ(2U, m.mChanges.size());
 
     // We still can't remove anything.
     proto.clear();
     m.appendUidMap(8, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
-    EXPECT_EQ(1, results.changes_size());
-    EXPECT_EQ(2U, m.mChanges.size());
+    ASSERT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(1, results.changes_size());
+    ASSERT_EQ(2U, m.mChanges.size());
 
     proto.clear();
     m.appendUidMap(9, config2, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
-    EXPECT_EQ(1, results.snapshots_size());
-    EXPECT_EQ(2, results.changes_size());
+    ASSERT_EQ(1, results.snapshots_size());
+    ASSERT_EQ(2, results.changes_size());
     // At this point both should be cleared.
-    EXPECT_EQ(0U, m.mChanges.size());
+    ASSERT_EQ(0U, m.mChanges.size());
 }
 
 TEST(UidMapTest, TestMemoryComputed) {
@@ -414,13 +408,13 @@
 
     m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2,
                 String16("v2"), String16(""));
-    EXPECT_EQ(1U, m.mChanges.size());
+    ASSERT_EQ(1U, m.mChanges.size());
 
     // Now force deletion by limiting the memory to hold one delta change.
     m.maxBytesOverride = 120; // Since the app string alone requires >45 characters.
     m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4,
                 String16("v4"), String16(""));
-    EXPECT_EQ(1U, m.mChanges.size());
+    ASSERT_EQ(1U, m.mChanges.size());
 }
 
 #else
diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
index e664023..64ea219 100644
--- a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
@@ -15,12 +15,14 @@
 #include "src/anomaly/AlarmTracker.h"
 
 #include <gtest/gtest.h>
+#include <log/log_time.h>
 #include <stdio.h>
 #include <vector>
 
 using namespace testing;
 using android::sp;
 using std::set;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
@@ -34,8 +36,9 @@
 
 TEST(AlarmTrackerTest, TestTriggerTimestamp) {
     sp<AlarmMonitor> subscriberAlarmMonitor =
-        new AlarmMonitor(100, [](const sp<IStatsCompanionService>&, int64_t){},
-                         [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(100,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     Alarm alarm;
     alarm.set_offset_millis(15 * MS_PER_SEC);
     alarm.set_period_millis(60 * 60 * MS_PER_SEC);  // 1hr
@@ -56,7 +59,7 @@
     currentTimeSec = startMillis / MS_PER_SEC + 7000;
     nextAlarmTime = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60;
     firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
-    EXPECT_EQ(firedAlarmSet.size(), 1u);
+    ASSERT_EQ(firedAlarmSet.size(), 1u);
     tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
     EXPECT_TRUE(firedAlarmSet.empty());
     EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime);
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index c10703c..0cc8af1 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -13,13 +13,15 @@
 // limitations under the License.
 
 #include "src/anomaly/AnomalyTracker.h"
-#include "../metrics/metrics_test_helper.h"
 
 #include <gtest/gtest.h>
 #include <math.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
 using std::set;
@@ -147,7 +149,7 @@
     std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
 
     // Start time with no events.
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
 
     // Event from bucket #0 occurs.
@@ -158,7 +160,7 @@
 
     // Adds past bucket #0
     anomalyTracker.addPastBucket(bucket0, 0);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
@@ -172,7 +174,7 @@
 
     // Adds past bucket #0 again. The sum does not change.
     anomalyTracker.addPastBucket(bucket0, 0);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
@@ -185,7 +187,7 @@
     // Adds past bucket #1.
     anomalyTracker.addPastBucket(bucket1, 1);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
@@ -199,7 +201,7 @@
     // Adds past bucket #1 again. Nothing changes.
     anomalyTracker.addPastBucket(bucket1, 1);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
@@ -212,7 +214,7 @@
     // Adds past bucket #2.
     anomalyTracker.addPastBucket(bucket2, 2);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
 
@@ -225,7 +227,7 @@
     // Adds bucket #3.
     anomalyTracker.addPastBucket(bucket3, 3L);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
 
@@ -238,7 +240,7 @@
     // Adds bucket #4.
     anomalyTracker.addPastBucket(bucket4, 4);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
 
@@ -251,7 +253,7 @@
     // Adds bucket #5.
     anomalyTracker.addPastBucket(bucket5, 5);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
 
@@ -292,7 +294,7 @@
     int64_t eventTimestamp6 = bucketSizeNs * 27 + 3;
 
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD}));
     detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1);
     checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
@@ -301,15 +303,15 @@
     // Add past bucket #9
     anomalyTracker.addPastBucket(bucket9, 9);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD}));
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
     detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
     checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
             {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
@@ -317,27 +319,27 @@
     // Add past bucket #16
     anomalyTracker.addPastBucket(bucket16, 16);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD}));
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
     // Within refractory period.
     detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3);
     checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec,
             {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
 
     // Add past bucket #18
     anomalyTracker.addPastBucket(bucket18, 18);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4);
@@ -347,11 +349,11 @@
     // Add bucket #18 again. Nothing changes.
     anomalyTracker.addPastBucket(bucket18, 18);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1);
@@ -362,12 +364,12 @@
     // Add past bucket #20
     anomalyTracker.addPastBucket(bucket20, 20);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5);
     checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec,
             {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
@@ -375,14 +377,14 @@
     // Add past bucket #25
     anomalyTracker.addPastBucket(bucket25, 25);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {},
             {keyA, keyB, keyC, keyD, keyE}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
             {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
@@ -391,9 +393,9 @@
     EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE},
             {keyA, keyB, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
             {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}});
 }
diff --git a/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
index 6529d65..1d501fd 100644
--- a/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
@@ -24,6 +24,7 @@
 using std::vector;
 
 #ifdef __ANDROID__
+
 TEST(ConditionTrackerTest, TestUnknownCondition) {
     LogicalOperation operation = LogicalOperation::AND;
 
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index e826a52..07b5311b 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/condition/SimpleConditionTracker.h"
+#include "stats_event.h"
 #include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
@@ -31,6 +32,8 @@
 namespace os {
 namespace statsd {
 
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
 
 const int ATTRIBUTION_NODE_FIELD_ID = 1;
@@ -57,23 +60,23 @@
     return simplePredicate;
 }
 
-void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) {
-    std::vector<AttributionNodeInternal> nodes;
-    for (size_t i = 0; i < uids.size(); ++i) {
-        AttributionNodeInternal node;
-        node.set_uid(uids[i]);
-        nodes.push_back(node);
-    }
-    event->write(nodes);  // attribution chain.
+void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp,
+                       const vector<int>& uids, const string& wl, int acquire) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+    vector<std::string> tags(uids.size()); // vector of empty strings
+    writeAttribution(statsEvent, uids, tags);
+
+    AStatsEvent_writeString(statsEvent, wl.c_str());
+    AStatsEvent_writeInt32(statsEvent, acquire);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-void makeWakeLockEvent(
-        LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) {
-    writeAttributionNodesToEvent(event, uids);
-    event->write(wl);
-    event->write(acquire);
-    event->init();
-}
+} // anonymous namespace
+
 
 std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey(
     const Position position,
@@ -109,6 +112,114 @@
     return outputKeyMap;
 }
 
+TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueFalse) {
+    SimplePredicate simplePredicate;
+    simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+    simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
+    simplePredicate.set_count_nesting(false);
+    simplePredicate.set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
+
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
+                                            0 /*tracker index*/, simplePredicate,
+                                            trackerNameIndexMap);
+
+    ConditionKey queryKey;
+    vector<sp<ConditionTracker>> allPredicates;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+
+    // Check that initial condition is false.
+    conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+    vector<MatchingState> matcherState;
+    vector<bool> changedCache(1, false);
+
+    // Matched stop event.
+    // Check that condition is still false.
+    unique_ptr<LogEvent> screenOffEvent =
+            CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);  // On matcher not matched
+    matcherState.push_back(MatchingState::kMatched);     // Off matcher matched
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // Matched start event.
+    // Check that condition has changed to true.
+    unique_ptr<LogEvent> screenOnEvent =
+            CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);     // On matcher matched
+    matcherState.push_back(MatchingState::kNotMatched);  // Off matcher not matched
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueUnknown) {
+    SimplePredicate simplePredicate;
+    simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+    simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
+    simplePredicate.set_count_nesting(false);
+    simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN);
+
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
+
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
+                                            0 /*tracker index*/, simplePredicate,
+                                            trackerNameIndexMap);
+
+    ConditionKey queryKey;
+    vector<sp<ConditionTracker>> allPredicates;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+
+    // Check that initial condition is unknown.
+    conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+
+    vector<MatchingState> matcherState;
+    vector<bool> changedCache(1, false);
+
+    // Matched stop event.
+    // Check that condition is changed to false.
+    unique_ptr<LogEvent> screenOffEvent =
+            CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);  // On matcher not matched
+    matcherState.push_back(MatchingState::kMatched);     // Off matcher matched
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // Matched start event.
+    // Check that condition has changed to true.
+    unique_ptr<LogEvent> screenOnEvent =
+            CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);     // On matcher matched
+    matcherState.push_back(MatchingState::kNotMatched);  // Off matcher not matched
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+}
+
 TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
     SimplePredicate simplePredicate;
     simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
@@ -124,7 +235,9 @@
                                             simplePredicate, trackerNameIndexMap);
     EXPECT_FALSE(conditionTracker.isSliced());
 
-    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    // This event is not accessed in this test besides dimensions which is why this is okay.
+    // This is technically an invalid LogEvent because we do not call parseBuffer.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
 
     vector<MatchingState> matcherState;
     matcherState.push_back(MatchingState::kNotMatched);
@@ -209,7 +322,9 @@
                                             trackerNameIndexMap);
     EXPECT_FALSE(conditionTracker.isSliced());
 
-    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    // This event is not accessed in this test besides dimensions which is why this is okay.
+    // This is technically an invalid LogEvent because we do not call parseBuffer.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
 
     // one matched start
     vector<MatchingState> matcherState;
@@ -266,11 +381,7 @@
 
 TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
     std::vector<sp<ConditionTracker>> allConditions;
-    for (Position position :
-            { Position::FIRST, Position::LAST}) {
-        vector<Matcher> dimensionInCondition;
-        std::unordered_set<HashableDimensionKey> dimensionKeys;
-
+    for (Position position : {Position::FIRST, Position::LAST}) {
         SimplePredicate simplePredicate = getWakeLockHeldCondition(
                 true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
                 position);
@@ -287,8 +398,8 @@
 
         std::vector<int> uids = {111, 222, 333};
 
-        LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event, uids, "wl1", 1);
+        LogEvent event1(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/1);
 
         // one matched start
         vector<MatchingState> matcherState;
@@ -299,36 +410,33 @@
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
         vector<bool> changedCache(1, false);
 
-        conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+        conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                            changedCache);
 
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
         } else {
-            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+            ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
             EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
         } else {
-            EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), uids.size());
+            EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(),
+                      uids.size());
         }
 
         // Now test query
         const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
 
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
         // another wake lock acquired by this uid
-        LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event2, uids, "wl2", 1);
+        LogEvent event2(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/1);
         matcherState.clear();
         matcherState.push_back(MatchingState::kMatched);
         matcherState.push_back(MatchingState::kNotMatched);
@@ -337,19 +445,18 @@
         conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
                                            changedCache);
         EXPECT_FALSE(changedCache[0]);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
         } else {
-            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+            ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
         EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
 
 
         // wake lock 1 release
-        LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event3, uids, "wl1", 0);  // now release it.
+        LogEvent event3(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/0);
         matcherState.clear();
         matcherState.push_back(MatchingState::kNotMatched);
         matcherState.push_back(MatchingState::kMatched);
@@ -359,17 +466,16 @@
                                            changedCache);
         // nothing changes, because wake lock 2 is still held for this uid
         EXPECT_FALSE(changedCache[0]);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
         } else {
-            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+            ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
         EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
 
-        LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event4, uids, "wl2", 0);  // now release it.
+        LogEvent event4(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/0);
         matcherState.clear();
         matcherState.push_back(MatchingState::kNotMatched);
         matcherState.push_back(MatchingState::kMatched);
@@ -377,21 +483,19 @@
         changedCache[0] = false;
         conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
                                            changedCache);
-        EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+        ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
         EXPECT_TRUE(changedCache[0]);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
             EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
         } else {
-            EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), uids.size());
+            EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(),
+                      uids.size());
         }
 
         // query again
         conditionCache[0] = ConditionState::kNotEvaluated;
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
     }
 
@@ -399,12 +503,10 @@
 
 TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
     std::vector<sp<ConditionTracker>> allConditions;
-    vector<Matcher> dimensionInCondition;
-    std::unordered_set<HashableDimensionKey> dimensionKeys;
 
-    SimplePredicate simplePredicate = getWakeLockHeldCondition(
-            true /*nesting*/, true /*default to false*/, false /*slice output by uid*/,
-            Position::ANY /* position */);
+    SimplePredicate simplePredicate =
+            getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/,
+                                     false /*slice output by uid*/, Position::ANY /* position */);
     string conditionName = "WL_HELD";
 
     unordered_map<int64_t, int> trackerNameIndexMap;
@@ -418,13 +520,13 @@
 
     EXPECT_FALSE(conditionTracker.isSliced());
 
-    std::vector<int> uid_list1 = {111, 1111, 11111};
+    std::vector<int> uids1 = {111, 1111, 11111};
     string uid1_wl1 = "wl1_1";
-    std::vector<int> uid_list2 = {222, 2222, 22222};
+    std::vector<int> uids2 = {222, 2222, 22222};
     string uid2_wl1 = "wl2_1";
 
-    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, /*acquire=*/1);
 
     // one matched start for uid1
     vector<MatchingState> matcherState;
@@ -435,24 +537,23 @@
     vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
     vector<bool> changedCache(1, false);
 
-    conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+    conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                        changedCache);
 
-    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
     EXPECT_TRUE(changedCache[0]);
 
     // Now test query
     ConditionKey queryKey;
     conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                    true, true,
-                                    conditionCache, dimensionKeys);
+    conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache);
     EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
     // another wake lock acquired by this uid
-    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, /*acquire=*/1);
+
     matcherState.clear();
     matcherState.push_back(MatchingState::kMatched);
     matcherState.push_back(MatchingState::kNotMatched);
@@ -463,8 +564,10 @@
     EXPECT_FALSE(changedCache[0]);
 
     // uid1 wake lock 1 release
-    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0);  // now release it.
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1,
+                      /*release=*/0);  // now release it.
+
     matcherState.clear();
     matcherState.push_back(MatchingState::kNotMatched);
     matcherState.push_back(MatchingState::kMatched);
@@ -475,8 +578,9 @@
     // nothing changes, because uid2 is still holding wl.
     EXPECT_FALSE(changedCache[0]);
 
-    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0);  // now release it.
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1,
+                      /*acquire=*/0);  // now release it.
     matcherState.clear();
     matcherState.push_back(MatchingState::kNotMatched);
     matcherState.push_back(MatchingState::kMatched);
@@ -484,27 +588,21 @@
     changedCache[0] = false;
     conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
                                        changedCache);
-    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+    ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
     EXPECT_TRUE(changedCache[0]);
 
     // query again
     conditionCache[0] = ConditionState::kNotEvaluated;
-    dimensionKeys.clear();
-    conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                    true, true,
-                                    conditionCache, dimensionKeys);
+    conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache);
     EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 }
 
 TEST(SimpleConditionTrackerTest, TestStopAll) {
     std::vector<sp<ConditionTracker>> allConditions;
-    for (Position position :
-            { Position::FIRST, Position::LAST }) {
-        vector<Matcher> dimensionInCondition;
-        std::unordered_set<HashableDimensionKey> dimensionKeys;
-        SimplePredicate simplePredicate = getWakeLockHeldCondition(
-                true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
-                position);
+    for (Position position : {Position::FIRST, Position::LAST}) {
+        SimplePredicate simplePredicate =
+                getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/,
+                                         true /*output slice by uid*/, position);
         string conditionName = "WL_HELD_BY_UID3";
 
         unordered_map<int64_t, int> trackerNameIndexMap;
@@ -516,11 +614,11 @@
                                                 0 /*condition tracker index*/, simplePredicate,
                                                 trackerNameIndexMap);
 
-        std::vector<int> uid_list1 = {111, 1111, 11111};
-        std::vector<int> uid_list2 = {222, 2222, 22222};
+        std::vector<int> uids1 = {111, 1111, 11111};
+        std::vector<int> uids2 = {222, 2222, 22222};
 
-        LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event, uid_list1, "wl1", 1);
+        LogEvent event1(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, "wl1", /*acquire=*/1);
 
         // one matched start
         vector<MatchingState> matcherState;
@@ -531,38 +629,36 @@
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
         vector<bool> changedCache(1, false);
 
-        conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+        conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                            changedCache);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
         } else {
-            EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size());
+            ASSERT_EQ(uids1.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
         {
-            if (position == Position::FIRST ||
-                position == Position::LAST) {
-                EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+            if (position == Position::FIRST || position == Position::LAST) {
+                ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
             } else {
-                EXPECT_EQ(uid_list1.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_EQ(uids1.size(),
+                          conditionTracker.getChangedToTrueDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
             }
         }
 
         // Now test query
-        const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
+        const auto queryKey = getWakeLockQueryKey(position, uids1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
 
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
         // another wake lock acquired by uid2
-        LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-        makeWakeLockEvent(&event2, uid_list2, "wl2", 1);
+        LogEvent event2(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1);
+
         matcherState.clear();
         matcherState.push_back(MatchingState::kMatched);
         matcherState.push_back(MatchingState::kNotMatched);
@@ -571,38 +667,34 @@
         changedCache[0] = false;
         conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
                                            changedCache);
-        if (position == Position::FIRST ||
-            position == Position::LAST) {
-            EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+        if (position == Position::FIRST || position == Position::LAST) {
+            ASSERT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
         } else {
-            EXPECT_EQ(uid_list1.size() + uid_list2.size(),
-                      conditionTracker.mSlicedConditionState.size());
+            ASSERT_EQ(uids1.size() + uids2.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
         {
-            if (position == Position::FIRST ||
-                position == Position::LAST) {
-                EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+            if (position == Position::FIRST || position == Position::LAST) {
+                ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
             } else {
-                EXPECT_EQ(uid_list2.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_EQ(uids2.size(),
+                          conditionTracker.getChangedToTrueDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
             }
         }
 
-
         // TEST QUERY
-        const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
+        const auto queryKey2 = getWakeLockQueryKey(position, uids2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
 
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
-
         // stop all event
-        LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+        LogEvent event3(/*uid=*/0, /*pid=*/0);
+        makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1);
+
         matcherState.clear();
         matcherState.push_back(MatchingState::kNotMatched);
         matcherState.push_back(MatchingState::kNotMatched);
@@ -613,32 +705,28 @@
         conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
                                            changedCache);
         EXPECT_TRUE(changedCache[0]);
-        EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+        ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
         {
             if (position == Position::FIRST || position == Position::LAST) {
-                EXPECT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size());
+                ASSERT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
             } else {
-                EXPECT_EQ(uid_list1.size() + uid_list2.size(),
+                EXPECT_EQ(uids1.size() + uids2.size(),
                           conditionTracker.getChangedToFalseDimensions(allConditions)->size());
                 EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
             }
         }
 
         // TEST QUERY
-        const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
+        const auto queryKey3 = getWakeLockQueryKey(position, uids1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
         // TEST QUERY
-        const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
+        const auto queryKey4 = getWakeLockQueryKey(position, uids2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
-        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
-                                        false, false,
-                                        conditionCache, dimensionKeys);
+        conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
     }
 }
diff --git a/cmds/statsd/tests/condition/StateTracker_test.cpp b/cmds/statsd/tests/condition/StateTracker_test.cpp
deleted file mode 100644
index 9a66254..0000000
--- a/cmds/statsd/tests/condition/StateTracker_test.cpp
+++ /dev/null
@@ -1,112 +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 "src/condition/StateTracker.h"
-#include "tests/statsd_test_util.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <numeric>
-#include <vector>
-
-using std::map;
-using std::unordered_map;
-using std::vector;
-
-#ifdef __ANDROID__
-namespace android {
-namespace os {
-namespace statsd {
-
-const int kUidProcTag = 27;
-
-SimplePredicate getUidProcStatePredicate() {
-    SimplePredicate simplePredicate;
-    simplePredicate.set_start(StringToId("UidProcState"));
-
-    simplePredicate.mutable_dimensions()->set_field(kUidProcTag);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(1);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(2);
-
-    simplePredicate.set_count_nesting(false);
-    return simplePredicate;
-}
-
-void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) {
-    event->write(uid);
-    event->write(state);
-    event->init();
-}
-
-TEST(StateTrackerTest, TestStateChange) {
-    int uid1 = 111;
-    int uid2 = 222;
-
-    int state1 = 1001;
-    int state2 = 1002;
-    unordered_map<int64_t, int> trackerNameIndexMap;
-    trackerNameIndexMap[StringToId("UidProcState")] = 0;
-    vector<Matcher> primaryFields;
-    primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
-    StateTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
-                         trackerNameIndexMap, primaryFields);
-
-    LogEvent event(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid1, state1, &event);
-
-    vector<MatchingState> matcherState;
-    matcherState.push_back(MatchingState::kMatched);
-    vector<sp<ConditionTracker>> allPredicates;
-    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
-
-    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_FALSE(changedCache[0]);
-
-    LogEvent event2(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid1, state2, &event2);
-
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-
-    LogEvent event3(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid2, state1, &event3);
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
index 9ea0b81..93b2783 100644
--- a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
@@ -52,9 +52,9 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size());
+    ASSERT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size());
 
     auto alarmTracker1 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[0];
     auto alarmTracker2 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[1];
@@ -68,7 +68,7 @@
     const int64_t alarmFiredTimestampSec0 = alarmTimestampSec1 + 5;
     auto alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan(
             static_cast<uint32_t>(alarmFiredTimestampSec0));
-    EXPECT_EQ(1u, alarmSet.size());
+    ASSERT_EQ(1u, alarmSet.size());
     processor->onPeriodicAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet);
     EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec());
     EXPECT_EQ(alarmTimestampSec1 + 30 * 60, alarmTracker2->getAlarmTimestampSec());
@@ -77,7 +77,7 @@
     const int64_t alarmFiredTimestampSec1 = alarmTimestampSec0 + 2 * 60 * 60 + 125;
     alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan(
             static_cast<uint32_t>(alarmFiredTimestampSec1));
-    EXPECT_EQ(2u, alarmSet.size());
+    ASSERT_EQ(2u, alarmSet.size());
     processor->onPeriodicAlarmFired(alarmFiredTimestampSec1 * NS_PER_SEC, alarmSet);
     EXPECT_EQ(alarmTimestampSec0 + 60 * 60 * 3, alarmTracker1->getAlarmTimestampSec());
     EXPECT_EQ(alarmTimestampSec1 + 30 * 60 * 5, alarmTracker2->getAlarmTimestampSec());
diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
index c78d99e..af9436b 100644
--- a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
@@ -14,6 +14,7 @@
 
 #include <gtest/gtest.h>
 
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "src/StatsLogProcessor.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
@@ -28,7 +29,7 @@
 
 namespace {
 
-StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) {
+StatsdConfig CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
@@ -39,14 +40,14 @@
     countMetric->set_id(123456);
     countMetric->set_what(wakelockAcquireMatcher.id());
     *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     countMetric->set_bucket(FIVE_MINUTES);
 
     auto alert = config.add_alert();
     alert->set_id(StringToId("alert"));
     alert->set_metric_id(123456);
     alert->set_num_buckets(num_buckets);
-    alert->set_refractory_period_secs(10);
+    alert->set_refractory_period_secs(refractory_period_sec);
     alert->set_trigger_if_sum_gt(threshold);
     return config;
 }
@@ -56,101 +57,115 @@
 TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) {
     const int num_buckets = 1;
     const int threshold = 3;
-    auto config = CreateStatsdConfig(num_buckets, threshold);
+    const int refractory_period_sec = 10;
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
     const uint64_t alert_id = config.alert(0).id();
-    const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
 
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
 
     sp<AnomalyTracker> anomalyTracker =
-        processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
 
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    std::vector<AttributionNodeInternal> attributions2 = {
-        CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")};
-    std::vector<AttributionNodeInternal> attributions3 = {
-        CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions4 = {
-        CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions5 = {
-        CreateAttribution(222, "GMSCoreModule1") };
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+    std::vector<int> attributionUids3 = {111, 333};
+    std::vector<string> attributionTags3 = {"App1", "App3"};
+    std::vector<int> attributionUids4 = {222, 333};
+    std::vector<string> attributionTags4 = {"GMSCoreModule1", "App3"};
+    std::vector<int> attributionUids5 = {222};
+    std::vector<string> attributionTags5 = {"GMSCoreModule1"};
 
-    FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
                            Value((int32_t)111));
     HashableDimensionKey whatKey1({fieldValue1});
     MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
 
-    FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+    FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
                            Value((int32_t)222));
     HashableDimensionKey whatKey2({fieldValue2});
     MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY);
 
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids4, attributionTags4,
+                                       "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
-    event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 3);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids5, attributionTags5,
+                                       "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
-    event = CreateAcquireWakelockEvent(attributions3, "wl1", bucketStartTimeNs + 4);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids3, attributionTags3,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 4);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids5, attributionTags5,
+                                       "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
     // Fired alarm and refractory period end timestamp updated.
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 5);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 5, attributionUids1, attributionTags1,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 100);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 100, attributionUids1, attributionTags1,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids1,
+                                       attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1,
+                                       attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + bucketSizeNs + 1);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids4,
+                                       attributionTags4, "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
-    event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids5,
+                                       attributionTags5, "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
-    event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 3);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 3, attributionUids5,
+                                       attributionTags5, "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
-    event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 4);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 4, attributionUids5,
+                                       attributionTags5, "wl2");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 4) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
@@ -159,79 +174,213 @@
 TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) {
     const int num_buckets = 3;
     const int threshold = 3;
-    auto config = CreateStatsdConfig(num_buckets, threshold);
+    const int refractory_period_sec = 10;
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
     const uint64_t alert_id = config.alert(0).id();
-    const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
 
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
 
     sp<AnomalyTracker> anomalyTracker =
-        processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
 
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
-    std::vector<AttributionNodeInternal> attributions2 = {
-        CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")};
-    std::vector<AttributionNodeInternal> attributions3 = {
-        CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions4 = {
-        CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions5 = {
-        CreateAttribution(222, "GMSCoreModule1") };
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
 
-    FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
                            Value((int32_t)111));
     HashableDimensionKey whatKey1({fieldValue1});
     MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
 
-    FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
-                           Value((int32_t)222));
-    HashableDimensionKey whatKey2({fieldValue2});
-    MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY);
-
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Fired alarm and refractory period end timestamp updated.
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 4);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids1, attributionTags1,
+                                       "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1,
+                                       attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids2,
+                                       attributionTags2, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, attributionUids2,
+                                       attributionTags2, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 2, attributionUids2,
+                                       attributionTags2, "wl1");
     processor->OnLogEvent(event.get());
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 3 * bucketSizeNs + 2) / NS_PER_SEC + 1,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 }
 
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    metadata::StatsMetadataList result;
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+    ASSERT_EQ(result.stats_metadata_size(), 0);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    sp<AnomalyTracker> anomalyTracker =
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+                           Value((int32_t)111));
+    HashableDimensionKey whatKey1({fieldValue1});
+    MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
+    processor->OnLogEvent(event.get());
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+    metadata::StatsMetadataList result;
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+    metadata::StatsMetadata statsMetadata = result.stats_metadata(0);
+    ASSERT_EQ(result.stats_metadata_size(), 1);
+    EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+    EXPECT_EQ(statsMetadata.config_key().uid(), configUid);
+
+    metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0);
+    ASSERT_EQ(statsMetadata.alert_metadata_size(), 1);
+    EXPECT_EQ(alertMetadata.alert_id(), alert_id);
+    metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0);
+    ASSERT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1);
+    EXPECT_EQ(keyedData.last_refractory_ends_sec(),
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeNs / NS_PER_SEC +
+              mockWallClockNs / NS_PER_SEC);
+
+    metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key();
+    metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0);
+    EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag());
+    EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField());
+    EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    sp<AnomalyTracker> anomalyTracker =
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+                           Value((int32_t)111));
+    HashableDimensionKey whatKey1({fieldValue1});
+    MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
+    processor->OnLogEvent(event.get());
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs);
+
+    auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC;
+    processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot);
+
+    sp<AnomalyTracker> anomalyTracker2 =
+                processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+    EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeSinceBoot / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeNs / NS_PER_SEC);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
index 50da9e2..95e30100 100644
--- a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
@@ -45,7 +45,7 @@
 
     auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
     FieldMatcher dimensions = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     dimensions.add_child()->set_field(3);  // The wakelock tag is set in field 3 of the wakelock.
     *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
     holdingWakelockPredicate.mutable_simple_predicate()->set_count_nesting(nesting);
@@ -57,7 +57,7 @@
     durationMetric->set_condition(screenIsOffPredicate.id());
     durationMetric->set_aggregation_type(aggregationType);
     *durationMetric->mutable_dimensions_in_what() =
-        CreateAttributionUidDimensions(android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+        CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     durationMetric->set_bucket(FIVE_MINUTES);
 
     auto alert = config.add_alert();
@@ -69,26 +69,28 @@
     return config;
 }
 
-}  // namespace
+std::vector<int> attributionUids1 = {111, 222};
+std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1"};
 
-std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                      CreateAttribution(222, "GMSCoreModule1")};
+std::vector<int> attributionUids2 = {111, 222};
+std::vector<string> attributionTags2 = {"App2", "GMSCoreModule1"};
 
-std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"),
-                                                      CreateAttribution(222, "GMSCoreModule1")};
+std::vector<int> attributionUids3 = {222};
+std::vector<string> attributionTags3 = {"GMSCoreModule1"};
 
-std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1")};
-
-MetricDimensionKey dimensionKey(
-    HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED,
-                                           (int32_t)0x02010101), Value((int32_t)111))}),
-    DEFAULT_DIMENSION_KEY);
+MetricDimensionKey dimensionKey1(
+        HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED,
+                                               (int32_t)0x02010101),
+                                         Value((int32_t)111))}),
+        DEFAULT_DIMENSION_KEY);
 
 MetricDimensionKey dimensionKey2(
-    HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED,
+    HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED,
                                            (int32_t)0x02010101), Value((int32_t)222))}),
     DEFAULT_DIMENSION_KEY);
 
+}  // namespace
+
 TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) {
     const int num_buckets = 1;
     const uint64_t threshold_ns = NS_PER_SEC;
@@ -98,172 +100,176 @@
 
     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
 
     sp<AnomalyTracker> anomalyTracker =
-        processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
 
     auto screen_on_event = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1);
+            bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     auto screen_off_event = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 10);
+            bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screen_on_event.get());
     processor->OnLogEvent(screen_off_event.get());
 
     // Acquire wakelock wl1.
-    auto acquire_event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 11);
+    auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1,
+                                                    attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event.
-    auto release_event = CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 101);
+    auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1,
+                                                    attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire wakelock wl1 within bucket #0.
-    acquire_event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 110);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids2,
+                                               attributionTags2, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns - 90) / NS_PER_SEC + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Release wakelock wl1. One anomaly detected.
-    release_event = CreateReleaseWakelockEvent(
-            attributions2, "wl1", bucketStartTimeNs + NS_PER_SEC + 109);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 109,
+                                               attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire wakelock wl1.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + NS_PER_SEC + 112);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 112,
+                                               attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     // Wakelock has been hold longer than the threshold in bucket #0. The alarm is set at the
     // end of the refractory period.
-    const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey);
+    const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey1);
     EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1,
               (uint32_t)alarmFiredTimestampSec0);
 
     // Anomaly alarm fired.
     auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan(
             static_cast<uint32_t>(alarmFiredTimestampSec0));
-    EXPECT_EQ(1u, alarmSet.size());
+    ASSERT_EQ(1u, alarmSet.size());
     processor->onAnomalyAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet);
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Release wakelock wl1.
-    release_event = CreateReleaseWakelockEvent(
-            attributions1, "wl1", alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1);
+    release_event =
+            CreateReleaseWakelockEvent(alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     // Within refractory period. No more anomaly detected.
     EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire wakelock wl1.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + bucketSizeNs -  5 * NS_PER_SEC - 11);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11,
+                                       attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(acquire_event.get());
-    const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey);
+    const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey1);
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC) / NS_PER_SEC,
               (uint64_t)alarmFiredTimestampSec1);
 
     // Release wakelock wl1.
-    release_event = CreateReleaseWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10);
+    release_event =
+            CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10,
+                                       attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10) / NS_PER_SEC + 1,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+                      (bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan(
-                static_cast<uint32_t>(alarmFiredTimestampSec1));
-    EXPECT_EQ(0u, alarmSet.size());
+            static_cast<uint32_t>(alarmFiredTimestampSec1));
+    ASSERT_EQ(0u, alarmSet.size());
 
     // Acquire wakelock wl1 near the end of bucket #0.
-    acquire_event = CreateAcquireWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 2);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 2,
+                                               attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC,
-               anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
     // Release the event at early bucket #1.
-    release_event = CreateReleaseWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1,
+                                               attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     // Anomaly detected when stopping the alarm. The refractory period does not change.
-    EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Condition changes to false.
-    screen_on_event = CreateScreenStateChangedEvent(
-        android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-        bucketStartTimeNs + 2 * bucketSizeNs + 20);
+    screen_on_event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 20,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     processor->OnLogEvent(screen_on_event.get());
-    EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 30);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 30,
+                                               attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(acquire_event.get());
     // The condition is false. Do not start the alarm.
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Condition turns true.
-    screen_off_event = CreateScreenStateChangedEvent(
-        android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-        bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC);
+    screen_off_event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screen_off_event.get());
     EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + threshold_ns) / NS_PER_SEC,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
     // Condition turns to false.
-    screen_on_event = CreateScreenStateChangedEvent(
-        android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-        bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1);
+    screen_on_event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     processor->OnLogEvent(screen_on_event.get());
     // Condition turns to false. Cancelled the alarm.
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     //  Detected one anomaly.
     EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+                      (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Condition turns to true again.
-    screen_off_event = CreateScreenStateChangedEvent(
-        android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-        bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2);
+    screen_off_event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screen_off_event.get());
     EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 2 + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
-    release_event = CreateReleaseWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC);
+    release_event =
+            CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC,
+                                       attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(release_event.get());
     EXPECT_EQ(refractory_period_sec +
-                    (bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC) / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+                      (bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC) / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 }
 
 TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) {
@@ -275,107 +281,115 @@
 
     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
 
     sp<AnomalyTracker> anomalyTracker =
-        processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
 
     auto screen_off_event = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1);
+            bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screen_off_event.get());
 
     // Acquire wakelock "wc1" in bucket #0.
-    auto acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + bucketSizeNs -  NS_PER_SEC / 2 - 1);
+    auto acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Release wakelock "wc1" in bucket #0.
-    auto release_event = CreateReleaseWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1);
+    auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1,
+                                                    attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire wakelock "wc1" in bucket #1.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 1);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1,
+                                               attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    release_event = CreateReleaseWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 100);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 100,
+                                               attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire wakelock "wc2" in bucket #2.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1,
+                                               attributionUids3, attributionTags3, "wl2");
     processor->OnLogEvent(acquire_event.get());
-    EXPECT_EQ((bucketStartTimeNs + 2 *  bucketSizeNs) / NS_PER_SEC + 2,
+    EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2,
               anomalyTracker->getAlarmTimestampSec(dimensionKey2));
     EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
     // Release wakelock "wc2" in bucket #2.
-    release_event = CreateReleaseWakelockEvent(
-        attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC);
+    release_event =
+            CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC,
+                                       attributionUids3, attributionTags3, "wl2");
     processor->OnLogEvent(release_event.get());
     EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2));
     EXPECT_EQ(refractory_period_sec +
-                   (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC) / NS_PER_SEC,
+                      (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC) / NS_PER_SEC,
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2));
 
     // Acquire wakelock "wc1" in bucket #2.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC,
+                                       attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Release wakelock "wc1" in bucket #2.
-    release_event = CreateReleaseWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC);
+    release_event =
+            CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC,
+                                       attributionUids2, attributionTags2, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec +
-                   (int64_t)(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC) / NS_PER_SEC + 1,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+                      (int64_t)(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC) /
+                              NS_PER_SEC +
+                      1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 4);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 4,
+                                       attributionUids3, attributionTags3, "wl2");
     processor->OnLogEvent(acquire_event.get());
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 5);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 5,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1,
               anomalyTracker->getAlarmTimestampSec(dimensionKey2));
 
-    release_event = CreateReleaseWakelockEvent(
-        attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs + 2);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs + 2,
+                                               attributionUids3, attributionTags3, "wl2");
     processor->OnLogEvent(release_event.get());
-    release_event = CreateReleaseWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs + 6);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs + 6,
+                                               attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2));
     // The buckets are not messed up across dimensions. Only one dimension has anomaly triggered.
-    EXPECT_EQ(refractory_period_sec +
-                   (int64_t)(bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+    EXPECT_EQ(refractory_period_sec + (int64_t)(bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC +
+                      1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 }
 
 TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) {
@@ -384,7 +398,7 @@
     auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, false);
     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
 
     const uint64_t alert_id = config.alert(0).id();
     const uint32_t refractory_period_sec = 3 * bucketSizeNs / NS_PER_SEC;
@@ -392,89 +406,94 @@
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+    ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
 
     sp<AnomalyTracker> anomalyTracker =
-        processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
 
     auto screen_off_event = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1);
+            bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screen_off_event.get());
 
     // Acquire wakelock "wc1" in bucket #0.
-    auto acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 100);
+    auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 100,
+                                                    attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Acquire the wakelock "wc1" again.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     // The alarm does not change.
     EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
-    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
+    EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // Anomaly alarm fired late.
     const int64_t firedAlarmTimestampNs = bucketStartTimeNs + 2 * bucketSizeNs - NS_PER_SEC;
     auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan(
             static_cast<uint32_t>(firedAlarmTimestampNs / NS_PER_SEC));
-    EXPECT_EQ(1u, alarmSet.size());
+    ASSERT_EQ(1u, alarmSet.size());
     processor->onAnomalyAlarmFired(firedAlarmTimestampNs, alarmSet);
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs - 100);
+    acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100,
+                                               attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    auto release_event = CreateReleaseWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1,
+                                                    attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
     // Within the refractory period. No anomaly.
     EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
     // A new wakelock, but still within refractory period.
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
-    release_event = CreateReleaseWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC);
+    release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC,
+                                               attributionUids1, attributionTags1, "wl1");
     // Still in the refractory period. No anomaly.
     processor->OnLogEvent(release_event.get());
     EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC,
-              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey));
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 5);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 5,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
-    release_event = CreateReleaseWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 4);
+    release_event =
+            CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 4,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(release_event.get());
-    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey));
+    EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 
-    acquire_event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 3);
+    acquire_event =
+            CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 3,
+                                       attributionUids1, attributionTags1, "wl1");
     processor->OnLogEvent(acquire_event.get());
     EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC,
-              anomalyTracker->getAlarmTimestampSec(dimensionKey));
+              anomalyTracker->getAlarmTimestampSec(dimensionKey1));
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 3382525..4c2caa9 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -48,22 +48,60 @@
     countMetric->set_what(wakelockAcquireMatcher.id());
     *countMetric->mutable_dimensions_in_what() =
         CreateAttributionUidAndTagDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {position});
+            util::WAKELOCK_STATE_CHANGED, {position});
     countMetric->set_bucket(FIVE_MINUTES);
     return config;
 }
 
+// GMS core node is in the middle.
+std::vector<int> attributionUids1 = {111, 222, 333};
+std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1", "App3"};
+
+// GMS core node is the last one.
+std::vector<int> attributionUids2 = {111, 333, 222};
+std::vector<string> attributionTags2 = {"App1", "App3", "GMSCoreModule1"};
+
+// GMS core node is the first one.
+std::vector<int> attributionUids3 = {222, 333};
+std::vector<string> attributionTags3 = {"GMSCoreModule1", "App3"};
+
+// Single GMS core node.
+std::vector<int> attributionUids4 = {222};
+std::vector<string> attributionTags4 = {"GMSCoreModule1"};
+
+// GMS core has another uid.
+std::vector<int> attributionUids5 = {111, 444, 333};
+std::vector<string> attributionTags5 = {"App1", "GMSCoreModule2", "App3"};
+
+// Multiple GMS core nodes.
+std::vector<int> attributionUids6 = {444, 222};
+std::vector<string> attributionTags6 = {"GMSCoreModule2", "GMSCoreModule1"};
+
+// No GMS core nodes
+std::vector<int> attributionUids7 = {111, 333};
+std::vector<string> attributionTags7 = {"App1", "App3"};
+
+std::vector<int> attributionUids8 = {111};
+std::vector<string> attributionTags8 = {"App1"};
+
+// GMS core node with isolated uid.
+const int isolatedUid = 666;
+std::vector<int> attributionUids9 = {isolatedUid};
+std::vector<string> attributionTags9 = {"GMSCoreModule3"};
+
+std::vector<int> attributionUids10 = {isolatedUid};
+std::vector<string> attributionTags10 = {"GMSCoreModule1"};
+
 }  // namespace
 
 TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) {
     auto config = CreateStatsdConfig(Position::FIRST);
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
     // Here it assumes that GMS core has two uids.
@@ -74,70 +112,34 @@
              String16("APP3")},
             {String16(""), String16(""), String16(""), String16("")});
 
-    // GMS core node is in the middle.
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(333, "App3")};
-
-    // GMS core node is the last one.
-    std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(333, "App3"),
-                                                          CreateAttribution(222, "GMSCoreModule1")};
-
-    // GMS core node is the first one.
-    std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(333, "App3")};
-
-    // Single GMS core node.
-    std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")};
-
-    // GMS core has another uid.
-    std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(444, "GMSCoreModule2"),
-                                                          CreateAttribution(333, "App3")};
-
-    // Multiple GMS core nodes.
-    std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"),
-                                                          CreateAttribution(222, "GMSCoreModule1")};
-
-    // No GMS core nodes.
-    std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")};
-
-    // GMS core node with isolated uid.
-    const int isolatedUid = 666;
-    std::vector<AttributionNodeInternal> attributions9 = {
-            CreateAttribution(isolatedUid, "GMSCoreModule3")};
-
     std::vector<std::unique_ptr<LogEvent>> events;
     // Events 1~4 are in the 1st bucket.
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 2));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 200));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions4, "wl1", bucketStartTimeNs + bucketSizeNs));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                                attributionTags1, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2,
+                                                attributionTags2, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1,
+                                                attributionUids3, attributionTags3, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4,
+                                                attributionTags4, "wl1"));
 
     // Events 5~8 are in the 3rd bucket.
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100));
-    events.push_back(CreateIsolatedUidChangedEvent(
-        isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1));
-    events.push_back(CreateIsolatedUidChangedEvent(
-        isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1,
+                                                attributionUids5, attributionTags5, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100,
+                                                attributionUids6, attributionTags6, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2,
+                                                attributionUids7, attributionTags7, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs,
+                                                attributionUids8, attributionTags8, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1,
+                                                attributionUids9, attributionTags9, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100,
+                                                attributionUids9, attributionTags9, "wl2"));
+    events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222,
+                                                   isolatedUid, true /*is_create*/));
+    events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222,
+                                                   isolatedUid, false /*is_create*/));
 
     sortLogEventsByTimestamp(&events);
 
@@ -146,37 +148,37 @@
     }
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(countMetrics.data_size(), 4);
+    ASSERT_EQ(countMetrics.data_size(), 4);
 
     auto data = countMetrics.data(0);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111,
-            "App1");
-    EXPECT_EQ(data.bucket_info_size(), 2);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(),
+                                          util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ASSERT_EQ(data.bucket_info_size(), 2);
     EXPECT_EQ(data.bucket_info(0).count(), 2);
     EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
     EXPECT_EQ(data.bucket_info(1).count(), 1);
-    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+              bucketStartTimeNs + 2 * bucketSizeNs);
     EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
 
     data = countMetrics.data(1);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222,
-            "GMSCoreModule1");
-    EXPECT_EQ(data.bucket_info_size(), 2);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(),
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ASSERT_EQ(data.bucket_info_size(), 2);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
     EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
@@ -185,33 +187,34 @@
     EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
 
     data = countMetrics.data(2);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222,
-            "GMSCoreModule3");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(),
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule3");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+              bucketStartTimeNs + 3 * bucketSizeNs);
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 4 * bucketSizeNs);
 
     data = countMetrics.data(3);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 444,
-            "GMSCoreModule2");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(),
+                                          util::WAKELOCK_STATE_CHANGED, 444,
+                                          "GMSCoreModule2");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+              bucketStartTimeNs + 2 * bucketSizeNs);
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
 }
 
 TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) {
     auto config = CreateStatsdConfig(Position::ALL);
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
     // Here it assumes that GMS core has two uids.
@@ -222,70 +225,34 @@
              String16("APP3")},
             {String16(""), String16(""), String16(""), String16("")});
 
-    // GMS core node is in the middle.
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(333, "App3")};
-
-    // GMS core node is the last one.
-    std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(333, "App3"),
-                                                          CreateAttribution(222, "GMSCoreModule1")};
-
-    // GMS core node is the first one.
-    std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(333, "App3")};
-
-    // Single GMS core node.
-    std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")};
-
-    // GMS core has another uid.
-    std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(444, "GMSCoreModule2"),
-                                                          CreateAttribution(333, "App3")};
-
-    // Multiple GMS core nodes.
-    std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"),
-                                                          CreateAttribution(222, "GMSCoreModule1")};
-
-    // No GMS core nodes.
-    std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(333, "App3")};
-    std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")};
-
-    // GMS core node with isolated uid.
-    const int isolatedUid = 666;
-    std::vector<AttributionNodeInternal> attributions9 = {
-            CreateAttribution(isolatedUid, "GMSCoreModule1")};
-
     std::vector<std::unique_ptr<LogEvent>> events;
     // Events 1~4 are in the 1st bucket.
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 2));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions2, "wl1", bucketStartTimeNs + 200));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions4, "wl1", bucketStartTimeNs + bucketSizeNs));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                                attributionTags1, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2,
+                                                attributionTags2, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1,
+                                                attributionUids3, attributionTags3, "wl1"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4,
+                                                attributionTags4, "wl1"));
 
     // Events 5~8 are in the 3rd bucket.
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1));
-    events.push_back(CreateAcquireWakelockEvent(
-        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100));
-    events.push_back(CreateIsolatedUidChangedEvent(
-        isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1));
-    events.push_back(CreateIsolatedUidChangedEvent(
-        isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1,
+                                                attributionUids5, attributionTags5, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100,
+                                                attributionUids6, attributionTags6, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2,
+                                                attributionUids7, attributionTags7, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs,
+                                                attributionUids8, attributionTags8, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1,
+                                                attributionUids10, attributionTags10, "wl2"));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100,
+                                                attributionUids10, attributionTags10, "wl2"));
+    events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222,
+                                                   isolatedUid, true /*is_create*/));
+    events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222,
+                                                   isolatedUid, false /*is_create*/));
 
     sortLogEventsByTimestamp(&events);
 
@@ -294,124 +261,109 @@
     }
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(countMetrics.data_size(), 6);
+    ASSERT_EQ(countMetrics.data_size(), 6);
 
     auto data = countMetrics.data(0);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
-    EXPECT_EQ(2, data.bucket_info_size());
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(),
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ASSERT_EQ(2, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
-              data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_EQ(1, data.bucket_info(1).count());
     EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
               data.bucket_info(1).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs,
-              data.bucket_info(1).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    ValidateUidDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0,
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1,
+                                          util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
     EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
 
     data = countMetrics.data(2);
-    ValidateUidDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 444);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0,
+                                          util::WAKELOCK_STATE_CHANGED, 444,
+                                          "GMSCoreModule2");
+    ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1,
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    ValidateUidDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0,
+                                          util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1,
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2,
+                                          util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(bucketStartTimeNs,
-              data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(4);
-    ValidateUidDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0,
+                                          util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1,
+                                          util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2,
+                                          util::WAKELOCK_STATE_CHANGED, 222,
+                                          "GMSCoreModule1");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(bucketStartTimeNs,
-              data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(5);
-    ValidateUidDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
-    ValidateUidDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
-    ValidateAttributionUidAndTagDimension(
-        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
-    EXPECT_EQ(data.bucket_info_size(), 1);
+    ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0,
+                                          util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 444);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1,
+                                          util::WAKELOCK_STATE_CHANGED, 444,
+                                          "GMSCoreModule2");
+    ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2,
+                                          util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    ASSERT_EQ(data.bucket_info_size(), 1);
     EXPECT_EQ(data.bucket_info(0).count(), 1);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index b98dc60..0bce0ba 100644
--- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -39,7 +39,7 @@
     countMetric->set_id(123456);
     countMetric->set_what(wakelockAcquireMatcher.id());
     *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     countMetric->set_bucket(FIVE_MINUTES);
 
     auto alert = config.add_alert();
@@ -64,40 +64,46 @@
     const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
 
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
 
-    FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
                            Value((int32_t)111));
     HashableDimensionKey whatKey1({fieldValue1});
     MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
 
-    FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+    FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
                            Value((int32_t)222));
     HashableDimensionKey whatKey2({fieldValue2});
     MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY);
 
-    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
 
-    event = CreateAcquireWakelockEvent(attributions1, "wl2", bucketStartTimeNs + bucketSizeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids1,
+                                       attributionTags1, "wl2");
     processor->OnLogEvent(event.get());
 
-    event = CreateAcquireWakelockEvent(
-        attributions1, "wl1", bucketStartTimeNs + 25 * bucketSizeNs + 2);
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * bucketSizeNs + 2, attributionUids1,
+                                       attributionTags1, "wl1");
     processor->OnLogEvent(event.get());
 
     EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC),
               processor->mMetricsManagers.begin()->second->getTtlEndNs());
-}
 
+    // Clear the data stored on disk as a result of the ttl.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true,
+                            ADB_DUMP, FAST, &buffer);
+}
 
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
new file mode 100644
index 0000000..04eb400
--- /dev/null
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2019, 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 <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/state/StateManager.h"
+#include "src/state/StateTracker.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+/**
+ * Tests the initial condition and condition after the first log events for
+ * count metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(CountMetricE2eTest, TestInitialConditionChanges) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+    auto screenOnPredicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = screenOnPredicate;
+
+    auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+    *config.add_predicate() = deviceUnpluggedPredicate;
+
+    auto screenOnOnBatteryPredicate = config.add_predicate();
+    screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+    screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+    addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+    // CountSyncStartWhileScreenOnOnBattery (CombinationCondition)
+    CountMetric* countMetric1 = config.add_count_metric();
+    countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery"));
+    countMetric1->set_what(syncStartMatcher.id());
+    countMetric1->set_condition(screenOnOnBatteryPredicate->id());
+    countMetric1->set_bucket(FIVE_MINUTES);
+
+    // CountSyncStartWhileOnBattery (SimpleCondition)
+    CountMetric* countMetric2 = config.add_count_metric();
+    countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen"));
+    countMetric2->set_what(syncStartMatcher.id());
+    countMetric2->set_condition(deviceUnpluggedPredicate.id());
+    countMetric2->set_bucket(FIVE_MINUTES);
+
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(2, metricsManager->mAllMetricProducers.size());
+
+    sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+    sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1];
+
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON);
+    processor->OnLogEvent(screenOnEvent.get());
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+    auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+            bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+    processor->OnLogEvent(pluggedUsbEvent.get());
+    EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+    auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+            bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+    processor->OnLogEvent(pluggedNoneEvent.get());
+    EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
+/**
+* Test a count metric that has one slice_by_state with no primary fields.
+*
+* Once the CountMetricProducer is initialized, it has one atom id in
+* mSlicedStateAtoms and no entries in mStateGroupMap.
+
+* One StateTracker tracks the state atom, and it has one listener which is the
+* CountMetricProducer that was initialized.
+*/
+TEST(CountMetricE2eTest, TestSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+
+    auto state = CreateScreenState();
+    *config.add_state() = state;
+
+    // Create count metric that slices by screen state.
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(syncStartMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    countMetric->add_slice_by_state(state.id());
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that CountMetricProducer was initialized correctly.
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+            x                x         x    x        x      x       (syncStartEvents)
+          |                                       |                 (ScreenIsOnEvent)
+                   |     |                                          (ScreenIsOffEvent)
+                                                        |           (ScreenDozeEvent)
+    */
+    // Initialize log events - first bucket.
+    std::vector<int> attributionUids1 = {123};
+    std::vector<string> attributionTags1 = {"App1"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 1:00
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 75 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 1:25
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 150 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 2:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 200 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 3:30
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 4:20
+
+    // Initialize log events - second bucket.
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 350 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 6:00
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 6:50
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 450 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 7:40
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 475 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 8:05
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 500 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN));  // 8:30
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 520 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 8:50
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
+
+    // For each CountMetricData, check StateValue info is correct and buckets
+    // have correct counts.
+    auto data = countMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(2, data.bucket_info(1).count());
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(1, data.bucket_info(1).count());
+}
+
+/**
+ * Test a count metric that has one slice_by_state with a mapping and no
+ * primary fields.
+ *
+ * Once the CountMetricProducer is initialized, it has one atom id in
+ * mSlicedStateAtoms and has one entry per state value in mStateGroupMap.
+ *
+ * One StateTracker tracks the state atom, and it has one listener which is the
+ * CountMetricProducer that was initialized.
+ */
+TEST(CountMetricE2eTest, TestSlicedStateWithMap) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+
+    int64_t screenOnId = 4444;
+    int64_t screenOffId = 9876;
+    auto state = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
+    *config.add_state() = state;
+
+    // Create count metric that slices by screen state with on/off map.
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(syncStartMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    countMetric->add_slice_by_state(state.id());
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    // Check that CountMetricProducer was initialized correctly.
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    StateMap map = state.map();
+    for (auto group : map.group()) {
+        for (auto value : group.value()) {
+            EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value),
+                      group.group_id());
+        }
+    }
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+      x   x     x       x    x   x      x         x         x       (syncStartEvents)
+     -----------------------------------------------------------SCREEN_OFF events
+             |                  |                                   (ScreenStateOffEvent = 1)
+       |                  |                                         (ScreenStateDozeEvent = 3)
+                                                |                   (ScreenStateDozeSuspendEvent =
+    4)
+     -----------------------------------------------------------SCREEN_ON events
+                   |                                       |        (ScreenStateOnEvent = 2)
+                      |                                             (ScreenStateVrEvent = 5)
+                                            |                       (ScreenStateOnSuspendEvent = 6)
+    */
+    // Initialize log events - first bucket.
+    std::vector<int> attributionUids1 = {123};
+    std::vector<string> attributionTags1 = {"App1"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 30 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 0:40
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 1:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 90 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:40
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 120 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 2:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 150 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 180 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_VR));  // 3:10
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 210 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 3:40
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 4:20
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 280 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 4:50
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 285 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 4:55
+
+    // Initialize log events - second bucket.
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 360 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 6:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND));  // 6:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 430 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND));  // 7:20
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 440 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 7:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 540 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 9:10
+    events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 570 * NS_PER_SEC, attributionUids1,
+                                          attributionTags1, "sync_name"));  // 9:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
+
+    // For each CountMetricData, check StateValue info is correct and buckets
+    // have correct counts.
+    auto data = countMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(1, data.bucket_info(1).count());
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(4, data.bucket_info(0).count());
+    EXPECT_EQ(2, data.bucket_info(1).count());
+}
+
+/**
+* Test a count metric that has one slice_by_state with a primary field.
+
+* Once the CountMetricProducer is initialized, it should have one
+* MetricStateLink stored. State querying using a non-empty primary key
+* should also work as intended.
+*/
+TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    auto state = CreateUidProcessState();
+    *config.add_state() = state;
+
+    // Create count metric that slices by uid process state.
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(appCrashMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    countMetric->add_slice_by_state(state.id());
+    MetricStateLink* stateLink = countMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Check that CountMetricProducer was initialized correctly.
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0);
+    ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1);
+
+    /*
+    NOTE: "1" or "2" represents the uid associated with the state/app crash event
+               bucket #1               bucket #2
+    |    1    2    3    4    5    6    7    8    9    10
+    |------------------------|-------------------------|--
+      1  1    1      1   1  2     1        1        2    (AppCrashEvents)
+     -----------------------------------------------------PROCESS STATE events
+           1               2                             (TopEvent = 1002)
+                       1             1                   (ForegroundServiceEvent = 1003)
+                                         2               (ImportantBackgroundEvent = 1006)
+       1          1                               1      (ImportantForegroundEvent = 1005)
+
+    Based on the diagram above, an AppCrashEvent querying for process state value would return:
+    - StateTracker::kStateUnknown
+    - Important foreground
+    - Top
+    - Important foreground
+    - Foreground service
+    - Top (both the app crash and state still have matching uid = 2)
+
+    - Foreground service
+    - Foreground service
+    - Important background
+    */
+    // Initialize log events - first bucket.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/));  // 0:30
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 0:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/));  // 1:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP));  // 1:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/));  // 2:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 2:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/));  // 3:30
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 210 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 3:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/));  // 4:20
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP));  // 4:50
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/));  // 4:55
+
+    // Initialize log events - second bucket.
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/));  // 6:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 6:40
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 430 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 7:20
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 440 * NS_PER_SEC, 1 /*uid*/));  // 7:30
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 9:10
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/));  // 9:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
+
+    // For each CountMetricData, check StateValue info is correct and buckets
+    // have correct counts.
+    auto data = countMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).count());
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(2, data.bucket_info(1).count());
+
+    data = countMetrics.data(3);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).count());
+
+    data = countMetrics.data(4);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+}
+
+TEST(CountMetricE2eTest, TestMultipleSlicedStates) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    int64_t screenOnId = 4444;
+    int64_t screenOffId = 9876;
+    auto state1 = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
+    *config.add_state() = state1;
+    auto state2 = CreateUidProcessState();
+    *config.add_state() = state2;
+
+    // Create count metric that slices by screen state with on/off map and
+    // slices by uid process state.
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(appCrashMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    countMetric->add_slice_by_state(state1.id());
+    countMetric->add_slice_by_state(state2.id());
+    MetricStateLink* stateLink = countMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were properly initialized.
+    EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Check that CountMetricProducer was initialized correctly.
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 2);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), UID_PROCESS_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1);
+    ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1);
+
+    StateMap map = state1.map();
+    for (auto group : map.group()) {
+        for (auto value : group.value()) {
+            EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value),
+                      group.group_id());
+        }
+    }
+
+    /*
+                 bucket #1                      bucket #2
+      |    1    2    3    4    5    6    7    8    9    10 (minutes)
+      |------------------------|------------------------|--
+        1  1    1     1    1  2     1        1         2   (AppCrashEvents)
+       ---------------------------------------------------SCREEN_OFF events
+             |                              |              (ScreenOffEvent = 1)
+         |              |                                  (ScreenDozeEvent = 3)
+       ---------------------------------------------------SCREEN_ON events
+                   |                              |        (ScreenOnEvent = 2)
+                                        |                  (ScreenOnSuspendEvent = 6)
+       ---------------------------------------------------PROCESS STATE events
+             1               2                             (TopEvent = 1002)
+                                      1                    (ForegroundServiceEvent = 1003)
+                                            2              (ImportantBackgroundEvent = 1006)
+       1          1                                   1    (ImportantForegroundEvent = 1005)
+
+       Based on the diagram above, Screen State / Process State pairs for each
+       AppCrashEvent are:
+       - StateTracker::kStateUnknown / important foreground
+       - off / important foreground
+       - off / Top
+       - on / important foreground
+       - off / important foreground
+       - off / top
+
+       - off / important foreground
+       - off / foreground service
+       - on / important background
+
+      */
+    // Initialize log events - first bucket.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 5 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 0:15
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 30 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 0:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/));  // 1:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP));  // 1:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 90 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/));  // 2:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 2:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 160 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:50
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 210 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 3:40
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/));  // 4:20
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP));  // 4:50
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/));  // 4:55
+
+    // Initialize log events - second bucket.
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/));  // 6:10
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 380 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 6:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND));  // 6:40
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 420 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 7:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 440 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 7:30
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 450 * NS_PER_SEC, 1 /*uid*/));  // 7:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 520 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 8:50
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 9:10
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/));  // 9:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(6, countMetrics.data_size());
+
+    // For each CountMetricData, check StateValue info is correct and buckets
+    // have correct counts.
+    auto data = countMetrics.data(0);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1, data.slice_by_state(0).value());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(1).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(3);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(1).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).count());
+
+    data = countMetrics.data(4);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(5);
+    ASSERT_EQ(2, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+    EXPECT_TRUE(data.slice_by_state(1).has_value());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).count());
+    EXPECT_EQ(1, data.bucket_info(1).count());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
deleted file mode 100644
index e4186b7..0000000
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
+++ /dev/null
@@ -1,961 +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 <gtest/gtest.h>
-
-#include "src/StatsLogProcessor.h"
-#include "src/stats_log_util.h"
-#include "tests/statsd_test_util.h"
-
-#include <vector>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-#ifdef __ANDROID__
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
-        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition,
-        bool hashStringInReport) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
-    if (addExtraDimensionInCondition) {
-        syncDimension->add_child()->set_field(2 /* name field*/);
-    }
-
-    config.set_hash_strings_in_metric_report(hashStringInReport);
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(StringToId("CombinationPredicate"));
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(combinationPredicate->id());
-    metric->set_aggregation_type(aggregationType);
-    auto dimensionWhat = metric->mutable_dimensions_in_what();
-    dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
-    dimensionWhat->add_child()->set_field(2);  // job name field.
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-/*
- The following test has the following input.
-
-{ 10000000002 10000000002 (8)9999[I], [S], job0[S], 1[I],  }
-{ 10000000010 10000000010 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I],  }
-{ 10000000011 10000000011 (29)1[I],  }
-{ 10000000040 10000000040 (29)2[I],  }
-{ 10000000050 10000000050 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I],  }
-{ 10000000101 10000000101 (8)9999[I], [S], job0[S], 0[I],  }
-{ 10000000102 10000000102 (29)1[I],  }
-{ 10000000200 10000000200 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I],  }
-{ 10000000201 10000000201 (8)9999[I], [S], job2[S], 1[I],  }
-{ 10000000400 10000000400 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 1[I],  }
-{ 10000000401 10000000401 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 1[I],  }
-{ 10000000450 10000000450 (29)2[I],  }
-{ 10000000500 10000000500 (8)9999[I], [S], job2[S], 0[I],  }
-{ 10000000600 10000000600 (8)8888[I], [S], job2[S], 1[I],  }
-{ 10000000650 10000000650 (29)1[I],  }
-{ 309999999999 309999999999 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 0[I],  }
-{ 310000000100 310000000100 (29)2[I],  }
-{ 310000000300 310000000300 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I],  }
-{ 310000000600 310000000600 (8)8888[I], [S], job1[S], 1[I],  }
-{ 310000000640 310000000640 (29)1[I],  }
-{ 310000000650 310000000650 (29)2[I],  }
-{ 310000000700 310000000700 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 0[I],  }
-{ 310000000850 310000000850 (8)8888[I], [S], job2[S], 0[I],  }
-{ 310000000900 310000000900 (8)8888[I], [S], job1[S], 0[I],  }
-*/
-TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) {
-    for (const bool hashStringInReport : { true, false }) {
-        for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) {
-            for (auto aggregationType : {DurationMetric::MAX_SPARSE, DurationMetric::SUM}) {
-                ConfigKey cfgKey;
-                auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
-                        aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension,
-                        hashStringInReport);
-                int64_t bucketStartTimeNs = 10000000000;
-                int64_t bucketSizeNs =
-                        TimeUnitToBucketSizeInMillis(
-                            config.duration_metric(0).bucket()) * 1000000LL;
-
-                auto processor = CreateStatsLogProcessor(
-                        bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-                EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-                EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-                std::vector<AttributionNodeInternal> attributions1 = {
-                        CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                        CreateAttribution(222, "GMSCoreModule2")};
-
-                std::vector<AttributionNodeInternal> attributions2 = {
-                        CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                        CreateAttribution(555, "GMSCoreModule2")};
-
-                std::vector<std::unique_ptr<LogEvent>> events;
-
-                events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                               bucketStartTimeNs + 11));
-                events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                               bucketStartTimeNs + 40));
-
-                events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                               bucketStartTimeNs + 102));
-                events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                               bucketStartTimeNs + 450));
-
-                events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                               bucketStartTimeNs + 650));
-                events.push_back(CreateScreenStateChangedEvent(
-                                    android::view::DISPLAY_STATE_ON,
-                                    bucketStartTimeNs + bucketSizeNs + 100));
-
-                events.push_back(CreateScreenStateChangedEvent(
-                                    android::view::DISPLAY_STATE_OFF,
-                                    bucketStartTimeNs + bucketSizeNs + 640));
-                events.push_back(CreateScreenStateChangedEvent(
-                                    android::view::DISPLAY_STATE_ON,
-                                    bucketStartTimeNs + bucketSizeNs + 650));
-
-                events.push_back(CreateStartScheduledJobEvent(
-                        {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2));
-                events.push_back(CreateFinishScheduledJobEvent(
-                        {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
-
-                events.push_back(CreateStartScheduledJobEvent(
-                        {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
-                events.push_back(CreateFinishScheduledJobEvent(
-                        {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
-
-                events.push_back(CreateStartScheduledJobEvent(
-                        {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
-                events.push_back(CreateFinishScheduledJobEvent(
-                        {CreateAttribution(8888, "")}, "job2",
-                        bucketStartTimeNs + bucketSizeNs + 850));
-
-                events.push_back(CreateStartScheduledJobEvent(
-                        {CreateAttribution(8888, "")}, "job1",
-                        bucketStartTimeNs + bucketSizeNs + 600));
-                events.push_back(CreateFinishScheduledJobEvent(
-                        {CreateAttribution(8888, "")}, "job1",
-                        bucketStartTimeNs + bucketSizeNs + 900));
-
-                events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                      bucketStartTimeNs + 10));
-                events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                    bucketStartTimeNs + 50));
-
-                events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                      bucketStartTimeNs + 200));
-                events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                    bucketStartTimeNs + bucketSizeNs + 300));
-
-                events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
-                                                      bucketStartTimeNs + 400));
-                events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
-                                                    bucketStartTimeNs + bucketSizeNs - 1));
-
-                events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                                      bucketStartTimeNs + 401));
-                events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                                    bucketStartTimeNs + bucketSizeNs + 700));
-
-                sortLogEventsByTimestamp(&events);
-
-                for (const auto& event : events) {
-                    processor->OnLogEvent(event.get());
-                }
-
-                ConfigMetricsReportList reports;
-                vector<uint8_t> buffer;
-                processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false,
-                                        true, ADB_DUMP, FAST, &buffer);
-                EXPECT_TRUE(buffer.size() > 0);
-                EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-                backfillDimensionPath(&reports);
-                backfillStringInReport(&reports);
-                backfillStartEndTimestamp(&reports);
-
-                EXPECT_EQ(reports.reports_size(), 1);
-                EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-                StatsLogReport::DurationMetricDataWrapper metrics;
-                sortMetricDataByDimensionsValue(
-                        reports.reports(0).metrics(0).duration_metrics(), &metrics);
-                if (aggregationType == DurationMetric::SUM) {
-                    EXPECT_EQ(metrics.data_size(), 4);
-                    auto data = metrics.data(0);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job0");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 111, "App1");
-                    EXPECT_EQ(data.bucket_info_size(), 1);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                        bucketStartTimeNs + bucketSizeNs);
-
-
-                    data = metrics.data(1);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job1");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 333, "App2");
-                    EXPECT_EQ(data.bucket_info_size(), 1);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                        bucketStartTimeNs + 2 * bucketSizeNs);
-
-                    data = metrics.data(2);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job2");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 111, "App1");
-                    EXPECT_EQ(data.bucket_info_size(), 2);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 650);
-                    EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-                    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + 2 * bucketSizeNs);
-
-                    data = metrics.data(3);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job2");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 333, "App2");
-                    EXPECT_EQ(data.bucket_info_size(), 2);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 650);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100 + 650 - 640);
-                    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + 2 * bucketSizeNs);
-                } else {
-                    EXPECT_EQ(metrics.data_size(), 4);
-                    auto data = metrics.data(0);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job0");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 111, "App1");
-                    EXPECT_EQ(data.bucket_info_size(), 1);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                        bucketStartTimeNs + bucketSizeNs);
-
-                    data = metrics.data(1);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job1");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 333, "App2");
-                    EXPECT_EQ(data.bucket_info_size(), 1);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                        bucketStartTimeNs + 2 * bucketSizeNs);
-
-                    data = metrics.data(2);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                              "job2");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 111, "App1");
-                    EXPECT_EQ(data.bucket_info_size(), 2);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201);
-                    EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 100);
-                    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + 2 * bucketSizeNs);
-
-                    data = metrics.data(3);
-                    EXPECT_EQ(data.dimensions_in_what().field(),
-                              android::util::SCHEDULED_JOB_STATE_CHANGED);
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                              2);  // job name field
-                    EXPECT_EQ(data.dimensions_in_what().value_tuple().
-                                    dimensions_value(0).value_str(),
-                              "job2");  // job name
-                    ValidateAttributionUidAndTagDimension(
-                            data.dimensions_in_condition(),
-                            android::util::SYNC_STATE_CHANGED, 333, "App2");
-                    EXPECT_EQ(data.bucket_info_size(), 2);
-                    EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401);
-                    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 110);
-                    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + bucketSizeNs);
-                    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                              bucketStartTimeNs + 2 * bucketSizeNs);
-                }
-            }
-        }
-    }
-}
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition(
-        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *dimensions = CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    if (addExtraDimensionInCondition) {
-        syncDimension->add_child()->set_field(2 /* name field*/);
-    }
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(StringToId("CombinationPredicate"));
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(combinationPredicate->id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    *links->mutable_fields_in_what() =
-            CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition) {
-    for (bool isFullLink : {true, false}) {
-        for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-            ConfigKey cfgKey;
-            auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition(
-                aggregationType, !isFullLink);
-            int64_t bucketStartTimeNs = 10000000000;
-            int64_t bucketSizeNs =
-                    TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-            auto processor = CreateStatsLogProcessor(
-                    bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-            EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-            EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-            std::vector<AttributionNodeInternal> attributions1 = {
-                    CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(222, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions2 = {
-                    CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions3 = {
-                    CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<std::unique_ptr<LogEvent>> events;
-
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 55));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + 120));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 121));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + 450));
-
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 501));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + bucketSizeNs + 100));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2",
-                    bucketStartTimeNs + bucketSizeNs + 850));
-
-            events.push_back(
-                CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                             bucketStartTimeNs + bucketSizeNs - 2));
-            events.push_back(
-                CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                              bucketStartTimeNs + bucketSizeNs + 900));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                  bucketStartTimeNs + 50));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                bucketStartTimeNs + 110));
-
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                                  bucketStartTimeNs + 300));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
-                                                  bucketStartTimeNs + 400));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs - 1));
-
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + 550));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + 800));
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + bucketSizeNs - 1));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-
-            sortLogEventsByTimestamp(&events);
-
-            for (const auto& event : events) {
-                processor->OnLogEvent(event.get());
-            }
-
-            ConfigMetricsReportList reports;
-            vector<uint8_t> buffer;
-            processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false,
-                                    true, ADB_DUMP, FAST, &buffer);
-            EXPECT_TRUE(buffer.size() > 0);
-            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-            backfillDimensionPath(&reports);
-            backfillStringInReport(&reports);
-            backfillStartEndTimestamp(&reports);
-
-            EXPECT_EQ(reports.reports_size(), 1);
-            EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-            StatsLogReport::DurationMetricDataWrapper metrics;
-            sortMetricDataByDimensionsValue(
-                    reports.reports(0).metrics(0).duration_metrics(), &metrics);
-
-            if (aggregationType == DurationMetric::SUM) {
-                EXPECT_EQ(metrics.data_size(), 3);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            } else {
-                EXPECT_EQ(metrics.data_size(), 3);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            }
-        }
-    }
-}
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_PartialLink_AND_CombinationCondition(
-        DurationMetric::AggregationType aggregationType, bool hashStringInReport) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *dimensions = CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    syncDimension->add_child()->set_field(2 /* name field*/);
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-
-    config.set_hash_strings_in_metric_report(hashStringInReport);
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(StringToId("CombinationPredicate"));
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(combinationPredicate->id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *metric->mutable_dimensions_in_condition() = *syncDimension;
-
-
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    *links->mutable_fields_in_what() =
-            CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition) {
-    for (const bool hashStringInReport : {true, false}) {
-        for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-            ConfigKey cfgKey;
-            auto config =
-                    CreateDurationMetricConfig_PartialLink_AND_CombinationCondition(
-                            aggregationType, hashStringInReport);
-            int64_t bucketStartTimeNs = 10000000000;
-            int64_t bucketSizeNs =
-                    TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-            auto processor = CreateStatsLogProcessor(
-                    bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-            EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-            EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-            std::vector<AttributionNodeInternal> attributions1 = {
-                    CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(222, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions2 = {
-                    CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions3 = {
-                    CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<std::unique_ptr<LogEvent>> events;
-
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 55));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + 120));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 121));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + 450));
-
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                           bucketStartTimeNs + 501));
-            events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                           bucketStartTimeNs + bucketSizeNs + 100));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2",
-                    bucketStartTimeNs + bucketSizeNs + 850));
-
-            events.push_back(
-                CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                             bucketStartTimeNs + bucketSizeNs - 2));
-            events.push_back(
-                CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                              bucketStartTimeNs + bucketSizeNs + 900));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                  bucketStartTimeNs + 50));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                bucketStartTimeNs + 110));
-
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                                  bucketStartTimeNs + 300));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
-                                                  bucketStartTimeNs + 400));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs - 1));
-
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + 550));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + 800));
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + bucketSizeNs - 1));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-
-            sortLogEventsByTimestamp(&events);
-
-            for (const auto& event : events) {
-                processor->OnLogEvent(event.get());
-            }
-
-            ConfigMetricsReportList reports;
-            vector<uint8_t> buffer;
-            processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false,
-                                    true, ADB_DUMP, FAST, &buffer);
-            EXPECT_TRUE(buffer.size() > 0);
-            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-            backfillDimensionPath(&reports);
-            backfillStringInReport(&reports);
-            backfillStartEndTimestamp(&reports);
-
-            EXPECT_EQ(reports.reports_size(), 1);
-            EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-            StatsLogReport::DurationMetricDataWrapper metrics;
-            sortMetricDataByDimensionsValue(
-                    reports.reports(0).metrics(0).duration_metrics(), &metrics);
-            if (aggregationType == DurationMetric::SUM) {
-                EXPECT_EQ(metrics.data_size(), 4);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
-                EXPECT_EQ("ReadEmail",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-                EXPECT_EQ("ReadDoc",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 600 + 50);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-                EXPECT_EQ("ReadEmail",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(3);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
-                EXPECT_EQ("ReadDoc",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            } else {
-                EXPECT_EQ(metrics.data_size(), 4);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
-                EXPECT_EQ("ReadEmail",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-                EXPECT_EQ("ReadDoc",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 50);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-                EXPECT_EQ("ReadEmail",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(3);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
-                EXPECT_EQ("ReadDoc",
-                          data.dimensions_in_condition().value_tuple().
-                                dimensions_value(1).value_str());
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            }
-        }
-    }
-}
-
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
deleted file mode 100644
index f3ecd56..0000000
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
+++ /dev/null
@@ -1,816 +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 <gtest/gtest.h>
-
-#include "src/StatsLogProcessor.h"
-#include "src/stats_log_util.h"
-#include "tests/statsd_test_util.h"
-
-#include <vector>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-#ifdef __ANDROID__
-
-namespace {
-
-StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher();
-    *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher;
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
-    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-    *config.add_predicate() = screenIsOffPredicate;
-
-    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
-    // The predicate is dimensioning by any attribution node and both by uid and tag.
-    *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
-            CreateAttributionUidAndTagDimensions(android::util::WAKELOCK_STATE_CHANGED,
-                                                 {Position::FIRST});
-    *config.add_predicate() = holdingWakelockPredicate;
-
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(987654);
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate);
-
-    auto metric = config.add_count_metric();
-    metric->set_id(StringToId("ScreenBrightnessChangeMetric"));
-    metric->set_what(screenBrightnessChangeAtomMatcher.id());
-    metric->set_condition(combinationPredicate->id());
-    *metric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */});
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
-    metric->set_bucket(FIVE_MINUTES);
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition) {
-    ConfigKey cfgKey;
-    auto config = CreateCountMetric_NoLink_CombinationCondition_Config();
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
-
-    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(222, "GMSCoreModule2")};
-
-    std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(555, "GMSCoreModule2")};
-
-    std::vector<std::unique_ptr<LogEvent>> events;
-    events.push_back(
-            CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 100));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + bucketSizeNs + 1));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 2 * bucketSizeNs - 10));
-
-    events.push_back(CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 200));
-    events.push_back(
-            CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1));
-
-    events.push_back(CreateAcquireWakelockEvent(attributions2, "wl2",
-                                                bucketStartTimeNs + bucketSizeNs - 100));
-    events.push_back(CreateReleaseWakelockEvent(attributions2, "wl2",
-                                                bucketStartTimeNs + 2 * bucketSizeNs - 50));
-
-    events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 11));
-    events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 101));
-    events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 201));
-    events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + 203));
-    events.push_back(
-            CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 99));
-    events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 2));
-    events.push_back(CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + bucketSizeNs - 1));
-    events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs + 2));
-    events.push_back(
-            CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 11));
-    events.push_back(
-            CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 9));
-    events.push_back(
-            CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 1));
-
-    sortLogEventsByTimestamp(&events);
-
-    for (const auto& event : events) {
-        processor->OnLogEvent(event.get());
-    }
-
-    ConfigMetricsReportList reports;
-    vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
-    EXPECT_TRUE(buffer.size() > 0);
-    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-    backfillDimensionPath(&reports);
-    backfillStringInReport(&reports);
-    backfillStartEndTimestamp(&reports);
-
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-
-    EXPECT_EQ(countMetrics.data_size(), 7);
-    auto data = countMetrics.data(0);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
-    EXPECT_FALSE(data.dimensions_in_condition().has_field());
-
-    data = countMetrics.data(1);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
-    ValidateAttributionUidDimension(data.dimensions_in_condition(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
-
-    data = countMetrics.data(2);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 3);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
-    ValidateAttributionUidDimension(data.dimensions_in_condition(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
-
-    data = countMetrics.data(3);
-    EXPECT_EQ(data.bucket_info_size(), 2);
-    EXPECT_EQ(data.bucket_info(0).count(), 2);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(1).count(), 1);
-    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
-    ValidateAttributionUidDimension(data.dimensions_in_condition(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 333);
-
-    data = countMetrics.data(4);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 2);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
-    EXPECT_FALSE(data.dimensions_in_condition().has_field());
-
-    data = countMetrics.data(5);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
-    ValidateAttributionUidDimension(data.dimensions_in_condition(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
-
-    data = countMetrics.data(6);
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
-    ValidateAttributionUidDimension(data.dimensions_in_condition(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 333);
-}
-
-namespace {
-
-StatsdConfig CreateCountMetric_Link_CombinationCondition() {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    auto appCrashMatcher = CreateProcessCrashAtomMatcher();
-    *config.add_atom_matcher() = appCrashMatcher;
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
-    syncDimension->add_child()->set_field(2 /* name field*/);
-
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(987654);
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_count_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("AppCrashMetric"));
-    metric->set_what(appCrashMatcher.id());
-    metric->set_condition(combinationPredicate->id());
-    *metric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */});
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-
-    // Links between crash atom and condition of app is in syncing.
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    dimensionWhat->add_child()->set_field(1);  // uid field.
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition) {
-    ConfigKey cfgKey;
-    auto config = CreateCountMetric_Link_CombinationCondition();
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs =
-            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
-
-    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(222, "GMSCoreModule2")};
-
-    std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"),
-                                                          CreateAttribution(222, "GMSCoreModule1"),
-                                                          CreateAttribution(555, "GMSCoreModule2")};
-
-    std::vector<std::unique_ptr<LogEvent>> events;
-
-    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11));
-    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101));
-    events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101));
-
-    events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201));
-    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211));
-    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211));
-
-    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401));
-    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401));
-    events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401));
-
-    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301));
-    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301));
-
-    events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701));
-
-    events.push_back(
-            CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + 100));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                   bucketStartTimeNs + 202));
-    events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   bucketStartTimeNs + bucketSizeNs + 700));
-
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
-    events.push_back(
-            CreateSyncEndEvent(attributions1, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300));
-
-    events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
-    events.push_back(
-            CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1));
-
-    events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400));
-    events.push_back(
-            CreateSyncEndEvent(attributions2, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 600));
-
-    sortLogEventsByTimestamp(&events);
-
-    for (const auto& event : events) {
-        processor->OnLogEvent(event.get());
-    }
-
-    ConfigMetricsReportList reports;
-    vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
-    EXPECT_TRUE(buffer.size() > 0);
-    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-    backfillDimensionPath(&reports);
-    backfillStringInReport(&reports);
-    backfillStartEndTimestamp(&reports);
-
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-
-    EXPECT_EQ(countMetrics.data_size(), 5);
-    auto data = countMetrics.data(0);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
-    EXPECT_FALSE(data.dimensions_in_condition().has_field());
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-
-    data = countMetrics.data(1);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
-    ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                          android::util::SYNC_STATE_CHANGED, 111, "App1");
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 2);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-
-    data = countMetrics.data(2);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222);
-    EXPECT_FALSE(data.dimensions_in_condition().has_field());
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 2);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-
-    data = countMetrics.data(3);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
-    ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                          android::util::SYNC_STATE_CHANGED, 333, "App2");
-    EXPECT_EQ(data.bucket_info_size(), 2);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(1).count(), 1);
-    EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
-
-    data = countMetrics.data(4);
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777);
-    EXPECT_FALSE(data.dimensions_in_condition().has_field());
-    EXPECT_EQ(data.bucket_info_size(), 1);
-    EXPECT_EQ(data.bucket_info(0).count(), 1);
-    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
-}
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition(
-        DurationMetric::AggregationType aggregationType) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
-    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto inBatterySaverModePredicate = CreateBatterySaverModePredicate();
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
-    syncDimension->add_child()->set_field(2 /* name field */);
-
-    *config.add_predicate() = inBatterySaverModePredicate;
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(987654);
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("BatterySaverModeDurationMetric"));
-    metric->set_what(inBatterySaverModePredicate.id());
-    metric->set_condition(combinationPredicate->id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) {
-    for (auto aggregationType : { DurationMetric::MAX_SPARSE, DurationMetric::SUM}) {
-        ConfigKey cfgKey;
-        auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType);
-        int64_t bucketStartTimeNs = 10000000000;
-        int64_t bucketSizeNs =
-                TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-        std::vector<AttributionNodeInternal> attributions1 = {
-                CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(222, "GMSCoreModule2")};
-
-        std::vector<AttributionNodeInternal> attributions2 = {
-                CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(555, "GMSCoreModule2")};
-
-        std::vector<std::unique_ptr<LogEvent>> events;
-
-        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1));
-        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101));
-        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110));
-
-        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201));
-        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500));
-
-        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600));
-        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850));
-
-        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870));
-        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900));
-
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       bucketStartTimeNs + 10));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                       bucketStartTimeNs + 100));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       bucketStartTimeNs + 202));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                       bucketStartTimeNs + bucketSizeNs + 800));
-
-        events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
-        events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                            bucketStartTimeNs + bucketSizeNs + 300));
-
-        events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
-        events.push_back(
-                CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1));
-
-        events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
-        events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                            bucketStartTimeNs + bucketSizeNs + 700));
-
-        sortLogEventsByTimestamp(&events);
-
-        for (const auto& event : events) {
-            processor->OnLogEvent(event.get());
-        }
-
-        ConfigMetricsReportList reports;
-        vector<uint8_t> buffer;
-        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                                ADB_DUMP, FAST, &buffer);
-        EXPECT_TRUE(buffer.size() > 0);
-        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-        backfillDimensionPath(&reports);
-        backfillStringInReport(&reports);
-        backfillStartEndTimestamp(&reports);
-
-        EXPECT_EQ(reports.reports_size(), 1);
-        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-        StatsLogReport::DurationMetricDataWrapper metrics;
-        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
-
-        EXPECT_EQ(metrics.data_size(), 3);
-        auto data = metrics.data(0);
-        EXPECT_FALSE(data.dimensions_in_what().has_field());
-        EXPECT_FALSE(data.dimensions_in_condition().has_field());
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        } else {
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        }
-
-        data = metrics.data(1);
-        EXPECT_FALSE(data.dimensions_in_what().has_field());
-        ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                              android::util::SYNC_STATE_CHANGED, 111, "App1");
-        EXPECT_EQ(data.bucket_info_size(), 2);
-
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
-        } else {
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 300);
-        }
-        EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-        EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + bucketSizeNs);
-        EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + bucketSizeNs);
-        EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + 2 * bucketSizeNs);
-
-        data = metrics.data(2);
-        EXPECT_FALSE(data.dimensions_in_what().has_field());
-        ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                              android::util::SYNC_STATE_CHANGED, 333, "App2");
-        EXPECT_EQ(data.bucket_info_size(), 2);
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-        } else {
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs + 700 - 600);
-        }
-        EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-        EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + bucketSizeNs);
-        EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + bucketSizeNs);
-        EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                  bucketStartTimeNs + 2 * bucketSizeNs);
-    }
-}
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_Link_CombinationCondition(
-        DurationMetric::AggregationType aggregationType) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
-    *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
-    syncDimension->add_child()->set_field(2 /* name field */);
-
-    auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
-    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
-            CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */});
-
-    *config.add_predicate() = screenIsOffPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-    *config.add_predicate() = isInBackgroundPredicate;
-    auto combinationPredicate = config.add_predicate();
-    combinationPredicate->set_id(987654);
-    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
-    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
-    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("AppInBackgroundMetric"));
-    metric->set_what(isInBackgroundPredicate.id());
-    metric->set_condition(combinationPredicate->id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */});
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-
-    // Links between crash atom and condition of app is in syncing.
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
-    dimensionWhat->add_child()->set_field(1);  // uid field.
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition) {
-    for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-        ConfigKey cfgKey;
-        auto config = CreateDurationMetricConfig_Link_CombinationCondition(aggregationType);
-        int64_t bucketStartTimeNs = 10000000000;
-        int64_t bucketSizeNs =
-                TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-        std::vector<AttributionNodeInternal> attributions1 = {
-                CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(222, "GMSCoreModule2")};
-
-        std::vector<AttributionNodeInternal> attributions2 = {
-                CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(555, "GMSCoreModule2")};
-
-        std::vector<std::unique_ptr<LogEvent>> events;
-
-        events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101));
-        events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110));
-
-        events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201));
-        events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100));
-
-        events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399));
-        events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800));
-
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       bucketStartTimeNs + 10));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                       bucketStartTimeNs + 100));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       bucketStartTimeNs + 202));
-        events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                       bucketStartTimeNs + bucketSizeNs + 801));
-
-        events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
-        events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                            bucketStartTimeNs + bucketSizeNs + 300));
-
-        events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
-        events.push_back(
-                CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1));
-
-        events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
-        events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                            bucketStartTimeNs + bucketSizeNs + 700));
-
-        sortLogEventsByTimestamp(&events);
-
-        for (const auto& event : events) {
-            processor->OnLogEvent(event.get());
-        }
-
-        ConfigMetricsReportList reports;
-        vector<uint8_t> buffer;
-        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                                ADB_DUMP, FAST, &buffer);
-        EXPECT_TRUE(buffer.size() > 0);
-        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-        backfillDimensionPath(&reports);
-        backfillStringInReport(&reports);
-        backfillStartEndTimestamp(&reports);
-
-        EXPECT_EQ(reports.reports_size(), 1);
-        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-        StatsLogReport::DurationMetricDataWrapper metrics;
-        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
-
-        EXPECT_EQ(metrics.data_size(), 3);
-        auto data = metrics.data(0);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
-        EXPECT_FALSE(data.dimensions_in_condition().has_field());
-        EXPECT_EQ(data.bucket_info_size(), 1);
-        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
-        EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-        EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
-
-        data = metrics.data(1);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
-        ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                              android::util::SYNC_STATE_CHANGED, 111, "App1");
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        } else {
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 100 - 201);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        }
-
-        data = metrics.data(2);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
-        ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                              android::util::SYNC_STATE_CHANGED, 333, "App2");
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        } else {
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 299);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        }
-    }
-}
-
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
deleted file mode 100644
index 489bb0b..0000000
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
+++ /dev/null
@@ -1,814 +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 <gtest/gtest.h>
-
-#include "src/StatsLogProcessor.h"
-#include "src/stats_log_util.h"
-#include "tests/statsd_test_util.h"
-
-#include <vector>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-#ifdef __ANDROID__
-
-namespace {
-
-StatsdConfig CreateDurationMetricConfig_NoLink_SimpleCondition(
-        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
-    if (addExtraDimensionInCondition) {
-        syncDimension->add_child()->set_field(2 /* name field*/);
-    }
-
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(isSyncingPredicate.id());
-    metric->set_aggregation_type(aggregationType);
-    auto dimensionWhat = metric->mutable_dimensions_in_what();
-    dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
-    dimensionWhat->add_child()->set_field(2);  // job name field.
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition) {
-    for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : {true, false}) {
-        for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-            ConfigKey cfgKey;
-            auto config = CreateDurationMetricConfig_NoLink_SimpleCondition(
-                    aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension);
-            int64_t bucketStartTimeNs = 10000000000;
-            int64_t bucketSizeNs =
-                    TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-            auto processor = CreateStatsLogProcessor(
-                    bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-            EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-            EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-            std::vector<AttributionNodeInternal> attributions1 = {
-                    CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(222, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions2 = {
-                    CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<std::unique_ptr<LogEvent>> events;
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 1));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                  bucketStartTimeNs + 10));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                bucketStartTimeNs + 50));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                  bucketStartTimeNs + 200));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                bucketStartTimeNs + bucketSizeNs + 300));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
-                                                  bucketStartTimeNs + 400));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs - 1));
-
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                                  bucketStartTimeNs + 401));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-
-            sortLogEventsByTimestamp(&events);
-
-            for (const auto& event : events) {
-                processor->OnLogEvent(event.get());
-            }
-
-            ConfigMetricsReportList reports;
-            vector<uint8_t> buffer;
-            processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false,
-                                    true, ADB_DUMP, FAST, &buffer);
-            EXPECT_TRUE(buffer.size() > 0);
-            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-            backfillDimensionPath(&reports);
-            backfillStringInReport(&reports);
-            backfillStartEndTimestamp(&reports);
-
-            EXPECT_EQ(reports.reports_size(), 1);
-            EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-            StatsLogReport::DurationMetricDataWrapper metrics;
-            sortMetricDataByDimensionsValue(
-                    reports.reports(0).metrics(0).duration_metrics(), &metrics);
-            if (aggregationType == DurationMetric::SUM) {
-                EXPECT_EQ(metrics.data_size(), 4);
-                auto data = metrics.data(0);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job0");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 111, "App1");
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job1");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 333, "App2");
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job2");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 111, "App1");
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(3);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job2");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 333, "App2");
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            } else {
-                EXPECT_EQ(metrics.data_size(), 4);
-                auto data = metrics.data(0);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job0");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 111, "App1");
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job1");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 333, "App2");
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job2");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 111, "App1");
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 300);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(3);
-                EXPECT_EQ(data.dimensions_in_what().field(),
-                          android::util::SCHEDULED_JOB_STATE_CHANGED);
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
-                          2);  // job name field
-                EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
-                          "job2");  // job name
-                ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
-                                                      android::util::SYNC_STATE_CHANGED, 333, "App2");
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 );
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            }
-        }
-    }
-}
-
-namespace {
-
-StatsdConfig createDurationMetric_Link_SimpleConditionConfig(
-        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *dimensions = CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    if (addExtraDimensionInCondition) {
-        syncDimension->add_child()->set_field(2 /* name field*/);
-    }
-
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(isSyncingPredicate.id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    *links->mutable_fields_in_what() =
-            CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition) {
-    for (bool isFullLink : {true, false}) {
-        for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-            ConfigKey cfgKey;
-            auto config = createDurationMetric_Link_SimpleConditionConfig(
-                    aggregationType, !isFullLink);
-            int64_t bucketStartTimeNs = 10000000000;
-            int64_t bucketSizeNs =
-                    TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-            auto processor = CreateStatsLogProcessor(
-                    bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-            EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-            EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-            std::vector<AttributionNodeInternal> attributions1 = {
-                    CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(222, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions2 = {
-                    CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<AttributionNodeInternal> attributions3 = {
-                    CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
-                    CreateAttribution(555, "GMSCoreModule2")};
-
-            std::vector<std::unique_ptr<LogEvent>> events;
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
-
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
-            events.push_back(CreateFinishScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
-            events.push_back(CreateStartScheduledJobEvent(
-                    {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
-            events.push_back(
-                CreateFinishScheduledJobEvent({CreateAttribution(333, "App2")}, "job2",
-                                               bucketStartTimeNs + bucketSizeNs + 850));
-
-            events.push_back(
-                CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                             bucketStartTimeNs + bucketSizeNs - 2));
-            events.push_back(
-                CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                              bucketStartTimeNs + bucketSizeNs + 900));
-
-            events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                                  bucketStartTimeNs + 50));
-            events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                                bucketStartTimeNs + 110));
-
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                                  bucketStartTimeNs + 300));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-            events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
-                                                  bucketStartTimeNs + 400));
-            events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs - 1));
-
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + 550));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + 800));
-            events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                                  bucketStartTimeNs + bucketSizeNs - 1));
-            events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                                bucketStartTimeNs + bucketSizeNs + 700));
-
-            sortLogEventsByTimestamp(&events);
-
-            for (const auto& event : events) {
-                processor->OnLogEvent(event.get());
-            }
-
-            ConfigMetricsReportList reports;
-            vector<uint8_t> buffer;
-            processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false,
-                                    true, ADB_DUMP, FAST, &buffer);
-            EXPECT_TRUE(buffer.size() > 0);
-            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-            backfillDimensionPath(&reports);
-            backfillStringInReport(&reports);
-            backfillStartEndTimestamp(&reports);
-
-            EXPECT_EQ(reports.reports_size(), 1);
-            EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-            StatsLogReport::DurationMetricDataWrapper metrics;
-            sortMetricDataByDimensionsValue(
-                    reports.reports(0).metrics(0).duration_metrics(), &metrics);
-
-            if (aggregationType == DurationMetric::SUM) {
-                EXPECT_EQ(metrics.data_size(), 3);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            } else {
-                EXPECT_EQ(metrics.data_size(), 3);
-                auto data = metrics.data(0);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                    bucketStartTimeNs + bucketSizeNs);
-
-                data = metrics.data(1);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-                EXPECT_EQ(data.bucket_info_size(), 2);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300);
-                EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
-                EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-
-                data = metrics.data(2);
-                ValidateAttributionUidDimension(
-                    data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-                EXPECT_EQ(data.bucket_info_size(), 1);
-                EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701);
-                EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + bucketSizeNs);
-                EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                          bucketStartTimeNs + 2 * bucketSizeNs);
-            }
-        }
-    }
-}
-
-namespace {
-
-StatsdConfig createDurationMetric_PartialLink_SimpleConditionConfig(
-        DurationMetric::AggregationType aggregationType) {
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
-    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
-
-    auto scheduledJobPredicate = CreateScheduledJobPredicate();
-    auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *dimensions = CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    dimensions->add_child()->set_field(2);  // job name field.
-
-    auto isSyncingPredicate = CreateIsSyncingPredicate();
-    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    syncDimension->add_child()->set_field(2 /* name field*/);
-
-    *config.add_predicate() = scheduledJobPredicate;
-    *config.add_predicate() = isSyncingPredicate;
-
-    auto metric = config.add_duration_metric();
-    metric->set_bucket(FIVE_MINUTES);
-    metric->set_id(StringToId("scheduledJob"));
-    metric->set_what(scheduledJobPredicate.id());
-    metric->set_condition(isSyncingPredicate.id());
-    metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *metric->mutable_dimensions_in_condition() = *syncDimension;
-
-    auto links = metric->add_links();
-    links->set_condition(isSyncingPredicate.id());
-    *links->mutable_fields_in_what() =
-            CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
-    *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
-    return config;
-}
-
-}  // namespace
-
-TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition) {
-    for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
-        ConfigKey cfgKey;
-        auto config = createDurationMetric_PartialLink_SimpleConditionConfig(
-                aggregationType);
-        int64_t bucketStartTimeNs = 10000000000;
-        int64_t bucketSizeNs =
-                TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
-
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
-        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
-        std::vector<AttributionNodeInternal> attributions1 = {
-                CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(222, "GMSCoreModule2")};
-
-        std::vector<AttributionNodeInternal> attributions2 = {
-                CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(555, "GMSCoreModule2")};
-
-        std::vector<AttributionNodeInternal> attributions3 = {
-                CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
-                CreateAttribution(555, "GMSCoreModule2")};
-
-        std::vector<std::unique_ptr<LogEvent>> events;
-
-        events.push_back(CreateStartScheduledJobEvent(
-                {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
-        events.push_back(CreateFinishScheduledJobEvent(
-                {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
-
-        events.push_back(CreateStartScheduledJobEvent(
-                {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
-        events.push_back(CreateFinishScheduledJobEvent(
-                {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
-        events.push_back(CreateStartScheduledJobEvent(
-                {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
-        events.push_back(CreateFinishScheduledJobEvent(
-                {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + bucketSizeNs + 850));
-
-        events.push_back(
-            CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                         bucketStartTimeNs + bucketSizeNs - 2));
-        events.push_back(
-            CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
-                                          bucketStartTimeNs + bucketSizeNs + 900));
-
-        events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
-                                              bucketStartTimeNs + 50));
-        events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
-                                            bucketStartTimeNs + 110));
-
-        events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
-                                              bucketStartTimeNs + 300));
-        events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
-                                            bucketStartTimeNs + bucketSizeNs + 700));
-        events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
-                                              bucketStartTimeNs + 400));
-        events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
-                                            bucketStartTimeNs + bucketSizeNs - 1));
-
-        events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                              bucketStartTimeNs + 550));
-        events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                            bucketStartTimeNs + 800));
-        events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
-                                              bucketStartTimeNs + bucketSizeNs - 1));
-        events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
-                                            bucketStartTimeNs + bucketSizeNs + 700));
-
-        sortLogEventsByTimestamp(&events);
-
-        for (const auto& event : events) {
-            processor->OnLogEvent(event.get());
-        }
-
-        ConfigMetricsReportList reports;
-        vector<uint8_t> buffer;
-        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                                ADB_DUMP, FAST, &buffer);
-        EXPECT_TRUE(buffer.size() > 0);
-        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
-        backfillDimensionPath(&reports);
-        backfillStringInReport(&reports);
-        backfillStartEndTimestamp(&reports);
-
-        EXPECT_EQ(reports.reports_size(), 1);
-        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-        StatsLogReport::DurationMetricDataWrapper metrics;
-        sortMetricDataByDimensionsValue(
-                reports.reports(0).metrics(0).duration_metrics(), &metrics);
-
-        if (aggregationType == DurationMetric::SUM) {
-            EXPECT_EQ(4, metrics.data_size());
-            auto data = metrics.data(0);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
-            EXPECT_EQ("ReadEmail",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                bucketStartTimeNs + bucketSizeNs);
-
-            data = metrics.data(1);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-            EXPECT_EQ("ReadDoc",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 400 - 100);
-
-            data = metrics.data(2);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-            EXPECT_EQ("ReadEmail",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-
-            data = metrics.data(3);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
-            EXPECT_EQ("ReadDoc",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        } else {
-            EXPECT_EQ(metrics.data_size(), 4);
-            auto data = metrics.data(0);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
-            EXPECT_EQ("ReadEmail",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                bucketStartTimeNs + bucketSizeNs);
-
-            data = metrics.data(1);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-            EXPECT_EQ("ReadDoc",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600);
-
-            data = metrics.data(2);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
-            EXPECT_EQ("ReadEmail",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 2);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300);
-            EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
-            EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-
-            data = metrics.data(3);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
-            ValidateAttributionUidDimension(
-                data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
-            EXPECT_EQ("ReadDoc",
-                      data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
-            EXPECT_EQ(data.bucket_info_size(), 1);
-            EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701);
-            EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + bucketSizeNs);
-            EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
-                      bucketStartTimeNs + 2 * bucketSizeNs);
-        }
-    }
-}
-
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 5da0fca..4efb038 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -14,12 +14,13 @@
 
 #include <gtest/gtest.h>
 
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -28,7 +29,7 @@
 
 TEST(DurationMetricE2eTest, TestOneBucket) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -45,9 +46,8 @@
     durationMetric->set_bucket(FIVE_MINUTES);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
 
-
-    const int64_t baseTimeNs = 0; // 0:00
-    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
     const int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -57,10 +57,10 @@
 
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
 
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
@@ -68,42 +68,43 @@
     std::unique_ptr<LogEvent> event;
 
     // Screen is off at start of bucket.
-    event = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_OFF, configAddedTimeNs); // 0:01
+    event = CreateScreenStateChangedEvent(configAddedTimeNs,
+                                          android::view::DISPLAY_STATE_OFF);  // 0:01
     processor->OnLogEvent(event.get());
 
     // Turn screen on.
-    const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs);
+    const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC;  // 0:11
+    event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(event.get());
 
     // Turn off screen 30 seconds after turning on.
-    const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs);
+    const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC;  // 0:41
+    event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(event.get());
 
-    event = CreateScreenBrightnessChangedEvent(64, durationEndNs + 1 * NS_PER_SEC); // 0:42
+    event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64);  // 0:42
     processor->OnLogEvent(event.get());
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true,
-                            ADB_DUMP, FAST, &buffer); // 5:01
+                            ADB_DUMP, FAST, &buffer);  // 5:01
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
 
-    const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
-            reports.reports(0).metrics(0).duration_metrics();
-    EXPECT_EQ(1, durationMetrics.data_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
-    EXPECT_EQ(1, data.bucket_info_size());
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
     EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
@@ -111,7 +112,7 @@
 
 TEST(DurationMetricE2eTest, TestTwoBuckets) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -128,9 +129,8 @@
     durationMetric->set_bucket(FIVE_MINUTES);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
 
-
-    const int64_t baseTimeNs = 0; // 0:00
-    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
     const int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -140,10 +140,10 @@
 
     auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
 
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
@@ -151,42 +151,43 @@
     std::unique_ptr<LogEvent> event;
 
     // Screen is off at start of bucket.
-    event = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_OFF, configAddedTimeNs); // 0:01
+    event = CreateScreenStateChangedEvent(configAddedTimeNs,
+                                          android::view::DISPLAY_STATE_OFF);  // 0:01
     processor->OnLogEvent(event.get());
 
     // Turn screen on.
-    const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs);
+    const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC;  // 0:11
+    event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(event.get());
 
     // Turn off screen 30 seconds after turning on.
-    const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs);
+    const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC;  // 0:41
+    event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(event.get());
 
-    event = CreateScreenBrightnessChangedEvent(64, durationEndNs + 1 * NS_PER_SEC); // 0:42
+    event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64);  // 0:42
     processor->OnLogEvent(event.get());
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false, true,
-                            ADB_DUMP, FAST, &buffer); // 10:01
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false,
+                            true, ADB_DUMP, FAST, &buffer);  // 10:01
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
 
-    const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
-            reports.reports(0).metrics(0).duration_metrics();
-    EXPECT_EQ(1, durationMetrics.data_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
-    EXPECT_EQ(1, data.bucket_info_size());
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(0, bucketInfo.bucket_num());
@@ -197,7 +198,7 @@
 
 TEST(DurationMetricE2eTest, TestWithActivation) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -220,7 +221,7 @@
     metric_activation1->set_metric_id(metricId);
     auto event_activation1 = metric_activation1->add_event_activation();
     event_activation1->set_atom_matcher_id(crashMatcher.id());
-    event_activation1->set_ttl_seconds(30); // 30 secs.
+    event_activation1->set_ttl_seconds(30);  // 30 secs.
 
     const int64_t bucketStartTimeNs = 10000000000;
     const int64_t bucketSizeNs =
@@ -237,30 +238,31 @@
     vector<int64_t> activeConfigsBroadcast;
 
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
-    processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00
+    processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);  // 0:00
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
 
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
-    EXPECT_EQ(eventActivationMap.size(), 1u);
+    ASSERT_EQ(eventActivationMap.size(), 1u);
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
@@ -269,25 +271,25 @@
     std::unique_ptr<LogEvent> event;
 
     // Turn screen off.
-    event = CreateScreenStateChangedEvent(
-            android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * NS_PER_SEC); // 0:02
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * NS_PER_SEC,
+                                          android::view::DISPLAY_STATE_OFF);  // 0:02
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 2 * NS_PER_SEC);
 
     // Turn screen on.
-    const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs);
-    processor.OnLogEvent(event.get());
+    const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC;  // 0:05
+    event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), durationStartNs);
 
     // Activate metric.
-    const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10
+    const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC;  // 0:10
     const int64_t activationEndNs =
-            activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40
-    event = CreateAppCrashEvent(111, activationStartNs);
-    processor.OnLogEvent(event.get());
+            activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC;  // 0:40
+    event = CreateAppCrashEvent(activationStartNs, 111);
+    processor.OnLogEvent(event.get(), activationStartNs);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs);
@@ -295,43 +297,43 @@
 
     // Expire activation.
     const int64_t expirationNs = activationEndNs + 7 * NS_PER_SEC;
-    event = CreateScreenBrightnessChangedEvent(64, expirationNs); // 0:47
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(expirationNs, 64);  // 0:47
+    processor.OnLogEvent(event.get(), expirationNs);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
-    EXPECT_EQ(eventActivationMap.size(), 1u);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(eventActivationMap.size(), 1u);
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
     // Turn off screen 10 seconds after activation expiration.
-    const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs);
-    processor.OnLogEvent(event.get());
+    const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC;  // 0:50
+    event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF);
+    processor.OnLogEvent(event.get(), durationEndNs);
 
     // Turn screen on.
-    const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, duration2StartNs);
-    processor.OnLogEvent(event.get());
+    const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC;  // 0:55
+    event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), duration2StartNs);
 
     // Turn off screen.
-    const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, duration2EndNs);
-    processor.OnLogEvent(event.get());
+    const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC;  // 1:05
+    event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF);
+    processor.OnLogEvent(event.get(), duration2EndNs);
 
     // Activate metric.
-    const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10
+    const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC;  // 1:10
     const int64_t activation2EndNs =
-            activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40
-    event = CreateAppCrashEvent(211, activation2StartNs);
-    processor.OnLogEvent(event.get());
+            activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC;  // 1:40
+    event = CreateAppCrashEvent(activation2StartNs, 211);
+    processor.OnLogEvent(event.get(), activation2StartNs);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, activation2StartNs);
@@ -340,22 +342,23 @@
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true,
-                            ADB_DUMP, FAST, &buffer); // 5:01
+                           ADB_DUMP, FAST, &buffer);  // 5:01
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
 
-    const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
-            reports.reports(0).metrics(0).duration_metrics();
-    EXPECT_EQ(1, durationMetrics.data_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
-    EXPECT_EQ(1, data.bucket_info_size());
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(0, bucketInfo.bucket_num());
@@ -366,7 +369,7 @@
 
 TEST(DurationMetricE2eTest, TestWithCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
@@ -390,10 +393,10 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     EXPECT_TRUE(metricsManager->isActive());
@@ -401,41 +404,47 @@
     EXPECT_TRUE(eventActivationMap.empty());
 
     int appUid = 123;
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")};
+    vector<int> attributionUids1 = {appUid};
+    vector<string> attributionTags1 = {"App1"};
 
-    auto event = CreateAcquireWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1,
+                                            attributionTags1,
+                                            "wl1");  // 0:10
     processor->OnLogEvent(event.get());
 
-    event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22
+    event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid);  // 0:22
     processor->OnLogEvent(event.get());
 
-    event = CreateMoveToForegroundEvent(
-            appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC,
+                                        appUid);  // 3:15
     processor->OnLogEvent(event.get());
 
-    event = CreateReleaseWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + 4 * 60 * NS_PER_SEC); // 4:00
+    event = CreateReleaseWakelockEvent(bucketStartTimeNs + 4 * 60 * NS_PER_SEC, attributionUids1,
+                                       attributionTags1,
+                                       "wl1");  // 4:00
     processor->OnLogEvent(event.get());
 
     vector<uint8_t> buffer;
     ConfigMetricsReportList reports;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
-    EXPECT_GT(buffer.size(), 0);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
 
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = durationMetrics.data(0);
 
     // Validate bucket info.
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos());
@@ -445,7 +454,7 @@
 
 TEST(DurationMetricE2eTest, TestWithSlicedCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
@@ -454,14 +463,14 @@
 
     auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
     // The predicate is dimensioning by first attribution node by uid.
-    FieldMatcher dimensions = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED,
+                                                             {Position::FIRST});
     *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
     *config.add_predicate() = holdingWakelockPredicate;
 
     auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
     *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
-        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
     *config.add_predicate() = isInBackgroundPredicate;
 
     auto durationMetric = config.add_duration_metric();
@@ -470,29 +479,28 @@
     durationMetric->set_condition(isInBackgroundPredicate.id());
     durationMetric->set_aggregation_type(DurationMetric::SUM);
     // The metric is dimensioning by first attribution node and only by uid.
-    *durationMetric->mutable_dimensions_in_what() =
-        CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     durationMetric->set_bucket(FIVE_MINUTES);
 
     // Links between wakelock state atom and condition of app is in background.
     auto links = durationMetric->add_links();
     links->set_condition(isInBackgroundPredicate.id());
     auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::WAKELOCK_STATE_CHANGED);
+    dimensionWhat->set_field(util::WAKELOCK_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
     *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
-            android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, { Position::FIRST });
+            util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
 
     ConfigKey cfgKey;
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     EXPECT_TRUE(metricsManager->isActive());
@@ -500,44 +508,47 @@
     EXPECT_TRUE(eventActivationMap.empty());
 
     int appUid = 123;
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")};
+    std::vector<int> attributionUids1 = {appUid};
+    std::vector<string> attributionTags1 = {"App1"};
 
-    auto event = CreateAcquireWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1,
+                                            attributionTags1, "wl1");  // 0:10
     processor->OnLogEvent(event.get());
 
-    event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22
+    event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid);  // 0:22
     processor->OnLogEvent(event.get());
 
-    event = CreateReleaseWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + 60 * NS_PER_SEC); // 1:00
+    event = CreateReleaseWakelockEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1,
+                                       attributionTags1, "wl1");  // 1:00
     processor->OnLogEvent(event.get());
 
-
-    event = CreateMoveToForegroundEvent(
-            appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC,
+                                        appUid);  // 3:15
     processor->OnLogEvent(event.get());
 
     vector<uint8_t> buffer;
     ConfigMetricsReportList reports;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
-    EXPECT_GT(buffer.size(), 0);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
 
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = durationMetrics.data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, appUid);
+                                    util::WAKELOCK_STATE_CHANGED, appUid);
     // Validate bucket info.
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos());
@@ -547,7 +558,7 @@
 
 TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
@@ -557,14 +568,14 @@
 
     auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
     // The predicate is dimensioning by first attribution node by uid.
-    FieldMatcher dimensions = CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED,
+                                                             {Position::FIRST});
     *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
     *config.add_predicate() = holdingWakelockPredicate;
 
     auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
     *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
-        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
     *config.add_predicate() = isInBackgroundPredicate;
 
     auto durationMetric = config.add_duration_metric();
@@ -573,19 +584,18 @@
     durationMetric->set_condition(isInBackgroundPredicate.id());
     durationMetric->set_aggregation_type(DurationMetric::SUM);
     // The metric is dimensioning by first attribution node and only by uid.
-    *durationMetric->mutable_dimensions_in_what() =
-        CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     durationMetric->set_bucket(FIVE_MINUTES);
 
     // Links between wakelock state atom and condition of app is in background.
     auto links = durationMetric->add_links();
     links->set_condition(isInBackgroundPredicate.id());
     auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::WAKELOCK_STATE_CHANGED);
+    dimensionWhat->set_field(util::WAKELOCK_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
     *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
-            android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, { Position::FIRST });
+            util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST});
 
     auto metric_activation1 = config.add_metric_activation();
     metric_activation1->set_metric_id(durationMetric->id());
@@ -598,25 +608,26 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
-    EXPECT_EQ(eventActivationMap.size(), 1u);
+    ASSERT_EQ(eventActivationMap.size(), 1u);
     EXPECT_TRUE(eventActivationMap.find(4) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[4]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
     int appUid = 123;
-    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")};
+    std::vector<int> attributionUids1 = {appUid};
+    std::vector<string> attributionTags1 = {"App1"};
 
-    auto event = CreateAcquireWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1,
+                                            attributionTags1, "wl1");  // 0:10
     processor->OnLogEvent(event.get());
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
@@ -624,7 +635,7 @@
     EXPECT_EQ(eventActivationMap[4]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
-    event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22
+    event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid);  // 0:22
     processor->OnLogEvent(event.get());
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
@@ -632,8 +643,8 @@
     EXPECT_EQ(eventActivationMap[4]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
-    const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC; // 0:30
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs);
+    const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC;  // 0:30
+    event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(event.get());
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
@@ -642,8 +653,8 @@
     EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
     const int64_t durationEndNs =
-            durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC; // 3:00
-    event = CreateAppCrashEvent(333, durationEndNs);
+            durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC;  // 3:00
+    event = CreateAppCrashEvent(durationEndNs, 333);
     processor->OnLogEvent(event.get());
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
@@ -651,24 +662,24 @@
     EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs);
     EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC);
 
-    event = CreateMoveToForegroundEvent(
-            appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC,
+                                        appUid);  // 3:15
     processor->OnLogEvent(event.get());
 
-    event = CreateReleaseWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC); // 4:17
+    event = CreateReleaseWakelockEvent(bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC,
+                                       attributionUids1, attributionTags1, "wl1");  // 4:17
     processor->OnLogEvent(event.get());
 
-    event = CreateMoveToBackgroundEvent(
-            appUid, bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC); // 4:20
+    event = CreateMoveToBackgroundEvent(bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC,
+                                        appUid);  // 4:20
     processor->OnLogEvent(event.get());
 
-    event = CreateAcquireWakelockEvent(
-            attributions1, "wl1", bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC); // 4:25
+    event = CreateAcquireWakelockEvent(bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC,
+                                       attributionUids1, attributionTags1, "wl1");  // 4:25
     processor->OnLogEvent(event.get());
 
-    const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC; // 4:30
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, duration2StartNs);
+    const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC;  // 4:30
+    event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(event.get());
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
@@ -678,24 +689,27 @@
 
     vector<uint8_t> buffer;
     ConfigMetricsReportList reports;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
-    EXPECT_GT(buffer.size(), 0);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
 
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = durationMetrics.data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, appUid);
+                                    util::WAKELOCK_STATE_CHANGED, appUid);
     // Validate bucket info.
-    EXPECT_EQ(2, data.bucket_info_size());
+    ASSERT_EQ(2, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos());
@@ -708,6 +722,748 @@
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos());
 }
 
+TEST(DurationMetricE2eTest, TestWithSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+      |          |                   |                              (ScreenIsOnEvent)
+           |                  |                                     (ScreenIsOffEvent)
+              |                                                     (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 2:10
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 310 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:20
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);  // 6:10
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(3, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(2);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+    *config.add_predicate() = deviceUnpluggedPredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that has a condition and slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->set_condition(deviceUnpluggedPredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |       1       2       3       4       5     6     7     8  (minutes)
+    |---------------------------------------|------------------
+             ON                          OFF    ON             (BatterySaverMode)
+                  T            F    T                          (DeviceUnpluggedPredicate)
+         |              |              |                       (ScreenIsOnEvent)
+                |           |                       |          (ScreenIsOffEvent)
+                                |                              (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 20 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:30
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 60 * NS_PER_SEC));  // 1:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 2:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 145 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:35
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 3:00
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_USB));  // 3:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 200 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 3:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 4:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 260 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 4:30
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 380 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 6:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(3, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(2);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    int64_t screenOnId = 4444;
+    int64_t screenOffId = 9876;
+    auto screenStateWithMap = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
+    *config.add_state() = screenStateWithMap;
+
+    // Create duration metric that slices by mapped screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenStateWithMap.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+     ---------------------------------------------------------SCREEN_OFF events
+           |                  |                                  (ScreenStateOffEvent = 1)
+              |                                                  (ScreenStateDozeEvent = 3)
+                                                |                (ScreenStateDozeSuspendEvent = 4)
+     ---------------------------------------------------------SCREEN_ON events
+      |          |                   |                           (ScreenStateOnEvent = 2)
+                      |                                          (ScreenStateVrEvent = 5)
+                                            |                    (ScreenStateOnSuspendEvent = 6)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 70 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:20
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 100 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:50
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_VR));                         // 3:00
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary 5:10.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 320 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND));  // 6:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 430 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND));  // 7:20
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(2, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(1);
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // This config is rejected because the dimension in what fields are not a superset of the sliced
+    // state primary fields.
+    ASSERT_EQ(processor->mMetricsManagers.size(), 0);
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The metric is dimensioning by first uid of attribution node and tag.
+    *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions(
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */});
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+    ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Initialize log events.
+    int appUid1 = 1001;
+    int appUid2 = 1002;
+    std::vector<int> attributionUids1 = {appUid1};
+    std::vector<string> attributionTags1 = {"App1"};
+
+    std::vector<int> attributionUids2 = {appUid2};
+    std::vector<string> attributionTags2 = {"App2"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 0:20
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock1"));  // 0:30
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 0:35
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 0:40
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock2"));  // 0:45
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:00
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 1:50
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 2:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 3:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(9, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(1);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(2);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(3);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(4);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(5);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(6);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(7);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = durationMetrics.data(8);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    ASSERT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index c7ba9be..1be2612 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -12,13 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
+using ::ndk::SharedRefBase;
 
 namespace android {
 namespace os {
@@ -29,12 +32,14 @@
 namespace {
 
 const int64_t metricId = 123456;
+const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE;
 
 StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type,
                                 bool useCondition = true) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
-    auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE);
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
+    auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG);
     *config.add_atom_matcher() = atomMatcher;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
@@ -51,7 +56,7 @@
     gaugeMetric->set_sampling_type(sampling_type);
     gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true);
     *gaugeMetric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
+            CreateDimensions(ATOM_TAG, {1 /* subsystem name */});
     gaugeMetric->set_bucket(FIVE_MINUTES);
     gaugeMetric->set_max_pull_delay_sec(INT_MAX);
     config.set_hash_strings_in_metric_report(false);
@@ -59,67 +64,67 @@
     return config;
 }
 
-}  // namespace
+}  // namespaces
 
 TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
 
     // When creating the config, the gauge metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& nextPullTimeNs =
             processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs;
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs);
 
-    auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                        configAddedTimeNs + 55);
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     // Pulling alarm arrives on time and reset the sequential pulling alarm.
     processor->informPullAlarmFired(nextPullTimeNs + 1);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs);
 
-    auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + bucketSizeNs + 10);
+    auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10,
+                                                       android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + bucketSizeNs + 100);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     processor->informPullAlarmFired(nextPullTimeNs + 1);
-    EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs,
-              nextPullTimeNs);
+    EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs);
 
     processor->informPullAlarmFired(nextPullTimeNs + 1);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs);
 
-    screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                  configAddedTimeNs + 3 * bucketSizeNs + 2);
+    screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2,
+                                                  android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
     processor->informPullAlarmFired(nextPullTimeNs + 3);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs);
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                  configAddedTimeNs + 5 * bucketSizeNs + 1);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     processor->informPullAlarmFired(nextPullTimeNs + 2);
@@ -136,70 +141,64 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-    EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 1);
 
     auto data = gaugeMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(6, data.bucket_info_size());
+    ASSERT_EQ(6, data.bucket_info_size());
 
-    EXPECT_EQ(1, data.bucket_info(0).atom_size());
-    EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(0).atom_size());
+    ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
     EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(1).atom_size());
-    EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1,
-              data.bucket_info(1).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(1).atom_size());
+    EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(2).atom_size());
-    EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1,
-              data.bucket_info(2).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(2).atom_size());
+    ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(3).atom_size());
-    EXPECT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1,
-              data.bucket_info(3).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(3).atom_size());
+    ASSERT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1, data.bucket_info(3).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(3).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(3).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(4).atom_size());
-    EXPECT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1,
-              data.bucket_info(4).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(4).atom_size());
+    ASSERT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(4).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(4).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(4).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(5).atom_size());
-    EXPECT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2,
-              data.bucket_info(5).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(5).atom_size());
+    ASSERT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2, data.bucket_info(5).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(5).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(5).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(5).atom(0).subsystem_sleep_state().subsystem_name().empty());
@@ -210,44 +209,45 @@
     auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
 
-    auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                        configAddedTimeNs + 55);
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
-    auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + bucketSizeNs + 10);
+    auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10,
+                                                       android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + bucketSizeNs + 100);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
-    screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                  configAddedTimeNs + 3 * bucketSizeNs + 2);
+    screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2,
+                                                  android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                  configAddedTimeNs + 5 * bucketSizeNs + 1);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
-    screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                  configAddedTimeNs + 5 * bucketSizeNs + 3);
+    screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 3,
+                                                  android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                  configAddedTimeNs + 5 * bucketSizeNs + 10);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 10,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     ConfigMetricsReportList reports;
@@ -259,45 +259,41 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-    EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 1);
 
     auto data = gaugeMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(3, data.bucket_info_size());
+    ASSERT_EQ(3, data.bucket_info_size());
 
-    EXPECT_EQ(1, data.bucket_info(0).atom_size());
-    EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(0).atom_size());
+    ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
     EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(1).atom_size());
-    EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100,
-              data.bucket_info(1).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(1).atom_size());
+    EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100, data.bucket_info(1).elapsed_timestamp_nanos(0));
     EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(2, data.bucket_info(2).atom_size());
-    EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1,
-              data.bucket_info(2).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10,
-              data.bucket_info(2).elapsed_timestamp_nanos(1));
+    ASSERT_EQ(2, data.bucket_info(2).atom_size());
+    ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0));
+    EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10, data.bucket_info(2).elapsed_timestamp_nanos(1));
     EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty());
@@ -306,48 +302,48 @@
     EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0);
 }
 
-
 TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
 
     // When creating the config, the gauge metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& nextPullTimeNs =
             processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs;
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs);
 
-    auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                        configAddedTimeNs + 55);
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
-    auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + bucketSizeNs + 10);
+    auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10,
+                                                       android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
     // Pulling alarm arrives one bucket size late.
     processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs);
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + 3 * bucketSizeNs + 11);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 11,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     // Pulling alarm arrives more than one bucket size late.
@@ -363,30 +359,29 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-    EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 1);
 
     auto data = gaugeMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(3, data.bucket_info_size());
+    ASSERT_EQ(3, data.bucket_info_size());
 
-    EXPECT_EQ(1, data.bucket_info(0).atom_size());
-    EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(0).atom_size());
+    ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
     EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(1).atom_size());
+    ASSERT_EQ(1, data.bucket_info(1).atom_size());
     EXPECT_EQ(configAddedTimeNs + 3 * bucketSizeNs + 11,
               data.bucket_info(1).elapsed_timestamp_nanos(0));
     EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
@@ -395,10 +390,9 @@
     EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(2).atom_size());
-    EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
-    EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12,
-              data.bucket_info(2).elapsed_timestamp_nanos(0));
+    ASSERT_EQ(1, data.bucket_info(2).atom_size());
+    ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+    EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12, data.bucket_info(2).elapsed_timestamp_nanos(0));
     EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty());
@@ -410,12 +404,11 @@
 
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
     auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = batterySaverStartMatcher;
-    const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets.
+    const int64_t ttlNs = 2 * bucketSizeNs;  // Two buckets.
     auto metric_activation = config.add_metric_activation();
     metric_activation->set_metric_id(metricId);
     metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY);
@@ -424,20 +417,22 @@
     event_activation->set_ttl_seconds(ttlNs / 1000000000);
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // When creating the config, the gauge metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& nextPullTimeNs =
@@ -446,27 +441,26 @@
 
     // Pulling alarm arrives on time and reset the sequential pulling alarm.
     // Event should not be kept.
-    processor->informPullAlarmFired(nextPullTimeNs + 1); // 15 mins + 1 ns.
+    processor->informPullAlarmFired(nextPullTimeNs + 1);  // 15 mins + 1 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // Activate the metric. A pull occurs upon activation.
-    const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis.
+    const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000);  // 2 millis.
     auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs);
-    processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms.
+    processor->OnLogEvent(batterySaverOnEvent.get());  // 15 mins + 2 ms.
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // This event should be kept. 2 total.
-    processor->informPullAlarmFired(nextPullTimeNs + 1); // 20 mins + 1 ns.
-    EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs,
-              nextPullTimeNs);
+    processor->informPullAlarmFired(nextPullTimeNs + 1);  // 20 mins + 1 ns.
+    EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs);
 
     // This event should be kept. 3 total.
-    processor->informPullAlarmFired(nextPullTimeNs + 2); // 25 mins + 2 ns.
+    processor->informPullAlarmFired(nextPullTimeNs + 2);  // 25 mins + 2 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs);
 
     // Create random event to deactivate metric.
-    auto deactivationEvent = CreateScreenBrightnessChangedEvent(50, activationNs + ttlNs + 1);
+    auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50);
     processor->OnLogEvent(deactivationEvent.get());
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
@@ -485,50 +479,49 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-    EXPECT_GT((int)gaugeMetrics.data_size(), 0);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 0);
 
     auto data = gaugeMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(3, data.bucket_info_size());
+    ASSERT_EQ(3, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
-    EXPECT_EQ(1, bucketInfo.atom_size());
-    EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, bucketInfo.atom_size());
+    ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
     EXPECT_EQ(activationNs, bucketInfo.elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
     EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0);
 
     bucketInfo = data.bucket_info(1);
-    EXPECT_EQ(1, bucketInfo.atom_size());
-    EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, bucketInfo.atom_size());
+    ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, bucketInfo.elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
     EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0);
 
     bucketInfo = data.bucket_info(2);
-    EXPECT_EQ(1, bucketInfo.atom_size());
-    EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, bucketInfo.atom_size());
+    ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 2, bucketInfo.elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
     EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)),
-            bucketInfo.start_bucket_elapsed_nanos());
+              bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(MillisToNano(NanoToMillis(activationNs + ttlNs + 1)),
-            bucketInfo.end_bucket_elapsed_nanos());
+              bucketInfo.end_bucket_elapsed_nanos());
     EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0);
 }
@@ -542,9 +535,10 @@
         TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             ATOM_TAG);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
@@ -554,7 +548,7 @@
 
     // When creating the config, the gauge metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& nextPullTimeNs =
@@ -578,43 +572,43 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
     sortMetricDataByDimensionsValue(
             reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-    EXPECT_GT((int)gaugeMetrics.data_size(), 0);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 0);
 
     auto data = gaugeMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(3, data.bucket_info_size());
+    ASSERT_EQ(3, data.bucket_info_size());
 
-    EXPECT_EQ(1, data.bucket_info(0).atom_size());
-    EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(0).atom_size());
+    ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
     EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(1).atom_size());
-    EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(1).atom_size());
+    ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty());
     EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0);
 
-    EXPECT_EQ(1, data.bucket_info(2).atom_size());
-    EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+    ASSERT_EQ(1, data.bucket_info(2).atom_size());
+    ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 4, data.bucket_info(2).elapsed_timestamp_nanos(0));
-    EXPECT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size());
+    ASSERT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
     EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty());
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index cd80310..a40a948 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -14,12 +14,13 @@
 
 #include <gtest/gtest.h>
 
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
 #include "src/stats_log_util.h"
+#include "stats_event.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -34,12 +35,12 @@
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
 
-    auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_OCCURRED);
+    auto atomMatcher = CreateSimpleAtomMatcher("", util::APP_START_OCCURRED);
     *config.add_atom_matcher() = atomMatcher;
 
     auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
     *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
-        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+        CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
     *config.add_predicate() = isInBackgroundPredicate;
 
     auto gaugeMetric = config.add_gauge_metric();
@@ -49,39 +50,43 @@
     gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false);
     gaugeMetric->set_sampling_type(sampling_type);
     auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
-    fieldMatcher->set_field(android::util::APP_START_OCCURRED);
+    fieldMatcher->set_field(util::APP_START_OCCURRED);
     fieldMatcher->add_child()->set_field(3);  // type (enum)
     fieldMatcher->add_child()->set_field(4);  // activity_name(str)
     fieldMatcher->add_child()->set_field(7);  // activity_start_msec(int64)
     *gaugeMetric->mutable_dimensions_in_what() =
-        CreateDimensions(android::util::APP_START_OCCURRED, {1 /* uid field */ });
+        CreateDimensions(util::APP_START_OCCURRED, {1 /* uid field */ });
     gaugeMetric->set_bucket(FIVE_MINUTES);
 
     auto links = gaugeMetric->add_links();
     links->set_condition(isInBackgroundPredicate.id());
     auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::APP_START_OCCURRED);
+    dimensionWhat->set_field(util::APP_START_OCCURRED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
     auto dimensionCondition = links->mutable_fields_in_condition();
-    dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     dimensionCondition->add_child()->set_field(1);  // uid field.
     return config;
 }
 
 std::unique_ptr<LogEvent> CreateAppStartOccurredEvent(
-    const int uid, const string& pkg_name, AppStartOccurred::TransitionType type,
-    const string& activity_name, const string& calling_pkg_name, const bool is_instant_app,
-    int64_t activity_start_msec, uint64_t timestampNs) {
-    auto logEvent = std::make_unique<LogEvent>(
-        android::util::APP_START_OCCURRED, timestampNs);
-    logEvent->write(uid);
-    logEvent->write(pkg_name);
-    logEvent->write(type);
-    logEvent->write(activity_name);
-    logEvent->write(calling_pkg_name);
-    logEvent->write(is_instant_app);
-    logEvent->write(activity_start_msec);
-    logEvent->init();
+        uint64_t timestampNs, const int uid, const string& pkg_name,
+        AppStartOccurred::TransitionType type, const string& activity_name,
+        const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::APP_START_OCCURRED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, pkg_name.c_str());
+    AStatsEvent_writeInt32(statsEvent, type);
+    AStatsEvent_writeString(statsEvent, activity_name.c_str());
+    AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str());
+    AStatsEvent_writeInt32(statsEvent, is_instant_app);
+    AStatsEvent_writeInt32(statsEvent, activity_start_msec);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -89,58 +94,57 @@
 
 TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) {
     for (const auto& sampling_type :
-            { GaugeMetric::FIRST_N_SAMPLES, GaugeMetric:: RANDOM_ONE_SAMPLE }) {
+         {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) {
         auto config = CreateStatsdConfigForPushedEvent(sampling_type);
         int64_t bucketStartTimeNs = 10000000000;
         int64_t bucketSizeNs =
-            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+                TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
 
         ConfigKey cfgKey;
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+        auto processor =
+                CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+        ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
         EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
         int appUid1 = 123;
         int appUid2 = 456;
         std::vector<std::unique_ptr<LogEvent>> events;
-        events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15));
-        events.push_back(CreateMoveToForegroundEvent(
-                appUid1, bucketStartTimeNs + bucketSizeNs + 250));
-        events.push_back(CreateMoveToBackgroundEvent(
-                appUid1, bucketStartTimeNs + bucketSizeNs + 350));
-        events.push_back(CreateMoveToForegroundEvent(
-            appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100));
-
+        events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid1));
+        events.push_back(
+                CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid1));
+        events.push_back(
+                CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid1));
+        events.push_back(
+                CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, appUid1));
 
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::WARM, "activity_name1", "calling_pkg_name1",
-            true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10));
+                bucketStartTimeNs + 10, appUid1, "app1", AppStartOccurred::WARM, "activity_name1",
+                "calling_pkg_name1", true /*is_instant_app*/, 101 /*activity_start_msec*/));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::HOT, "activity_name2", "calling_pkg_name2",
-            true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20));
+                bucketStartTimeNs + 20, appUid1, "app1", AppStartOccurred::HOT, "activity_name2",
+                "calling_pkg_name2", true /*is_instant_app*/, 102 /*activity_start_msec*/));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::COLD, "activity_name3", "calling_pkg_name3",
-            true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30));
+                bucketStartTimeNs + 30, appUid1, "app1", AppStartOccurred::COLD, "activity_name3",
+                "calling_pkg_name3", true /*is_instant_app*/, 103 /*activity_start_msec*/));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::WARM, "activity_name4", "calling_pkg_name4",
-            true /*is_instant_app*/, 104 /*activity_start_msec*/,
-            bucketStartTimeNs + bucketSizeNs + 30));
+                bucketStartTimeNs + bucketSizeNs + 30, appUid1, "app1", AppStartOccurred::WARM,
+                "activity_name4", "calling_pkg_name4", true /*is_instant_app*/,
+                104 /*activity_start_msec*/));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::COLD, "activity_name5", "calling_pkg_name5",
-            true /*is_instant_app*/, 105 /*activity_start_msec*/,
-            bucketStartTimeNs + 2 * bucketSizeNs));
+                bucketStartTimeNs + 2 * bucketSizeNs, appUid1, "app1", AppStartOccurred::COLD,
+                "activity_name5", "calling_pkg_name5", true /*is_instant_app*/,
+                105 /*activity_start_msec*/));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid1, "app1", AppStartOccurred::HOT, "activity_name6", "calling_pkg_name6",
-            false /*is_instant_app*/, 106 /*activity_start_msec*/,
-            bucketStartTimeNs + 2 * bucketSizeNs + 10));
+                bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid1, "app1", AppStartOccurred::HOT,
+                "activity_name6", "calling_pkg_name6", false /*is_instant_app*/,
+                106 /*activity_start_msec*/));
 
-        events.push_back(CreateMoveToBackgroundEvent(
-                appUid2, bucketStartTimeNs + bucketSizeNs + 10));
+        events.push_back(
+                CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 10, appUid2));
         events.push_back(CreateAppStartOccurredEvent(
-            appUid2, "app2", AppStartOccurred::COLD, "activity_name7", "calling_pkg_name7",
-            true /*is_instant_app*/, 201 /*activity_start_msec*/,
-            bucketStartTimeNs + 2 * bucketSizeNs + 10));
+                bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid2, "app2", AppStartOccurred::COLD,
+                "activity_name7", "calling_pkg_name7", true /*is_instant_app*/,
+                201 /*activity_start_msec*/));
 
         sortLogEventsByTimestamp(&events);
 
@@ -149,31 +153,31 @@
         }
         ConfigMetricsReportList reports;
         vector<uint8_t> buffer;
-        processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true,
-                                ADB_DUMP, FAST, &buffer);
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP,
+                                FAST, &buffer);
         EXPECT_TRUE(buffer.size() > 0);
         EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
         backfillDimensionPath(&reports);
         backfillStringInReport(&reports);
         backfillStartEndTimestamp(&reports);
-        EXPECT_EQ(1, reports.reports_size());
-        EXPECT_EQ(1, reports.reports(0).metrics_size());
+        ASSERT_EQ(1, reports.reports_size());
+        ASSERT_EQ(1, reports.reports(0).metrics_size());
         StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
-        sortMetricDataByDimensionsValue(
-                reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
-        EXPECT_EQ(2, gaugeMetrics.data_size());
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(),
+                                        &gaugeMetrics);
+        ASSERT_EQ(2, gaugeMetrics.data_size());
 
         auto data = gaugeMetrics.data(0);
-        EXPECT_EQ(android::util::APP_START_OCCURRED, data.dimensions_in_what().field());
-        EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+        EXPECT_EQ(util::APP_START_OCCURRED, data.dimensions_in_what().field());
+        ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
         EXPECT_EQ(1 /* uid field */,
                   data.dimensions_in_what().value_tuple().dimensions_value(0).field());
         EXPECT_EQ(appUid1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-        EXPECT_EQ(3, data.bucket_info_size());
+        ASSERT_EQ(3, data.bucket_info_size());
         if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) {
-            EXPECT_EQ(2, data.bucket_info(0).atom_size());
-            EXPECT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size());
-            EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+            ASSERT_EQ(2, data.bucket_info(0).atom_size());
+            ASSERT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
                       data.bucket_info(0).end_bucket_elapsed_nanos());
@@ -190,8 +194,8 @@
             EXPECT_EQ(103L,
                       data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis());
 
-            EXPECT_EQ(1, data.bucket_info(1).atom_size());
-            EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(1, data.bucket_info(1).atom_size());
+            ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
                       data.bucket_info(1).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
@@ -203,8 +207,8 @@
             EXPECT_EQ(104L,
                       data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis());
 
-            EXPECT_EQ(2, data.bucket_info(2).atom_size());
-            EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(2, data.bucket_info(2).atom_size());
+            ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
                       data.bucket_info(2).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
@@ -222,8 +226,8 @@
             EXPECT_EQ(106L,
                       data.bucket_info(2).atom(1).app_start_occurred().activity_start_millis());
         } else {
-            EXPECT_EQ(1, data.bucket_info(0).atom_size());
-            EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(1, data.bucket_info(0).atom_size());
+            ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
                       data.bucket_info(0).end_bucket_elapsed_nanos());
@@ -234,8 +238,8 @@
             EXPECT_EQ(102L,
                       data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis());
 
-            EXPECT_EQ(1, data.bucket_info(1).atom_size());
-            EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(1, data.bucket_info(1).atom_size());
+            ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
                       data.bucket_info(1).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
@@ -247,8 +251,8 @@
             EXPECT_EQ(104L,
                       data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis());
 
-            EXPECT_EQ(1, data.bucket_info(2).atom_size());
-            EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+            ASSERT_EQ(1, data.bucket_info(2).atom_size());
+            ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
             EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
                       data.bucket_info(2).start_bucket_elapsed_nanos());
             EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
@@ -263,14 +267,14 @@
 
         data = gaugeMetrics.data(1);
 
-        EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_OCCURRED);
-        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+        EXPECT_EQ(data.dimensions_in_what().field(), util::APP_START_OCCURRED);
+        ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
         EXPECT_EQ(1 /* uid field */,
                   data.dimensions_in_what().value_tuple().dimensions_value(0).field());
         EXPECT_EQ(appUid2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-        EXPECT_EQ(1, data.bucket_info_size());
-        EXPECT_EQ(1, data.bucket_info(0).atom_size());
-        EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+        ASSERT_EQ(1, data.bucket_info_size());
+        ASSERT_EQ(1, data.bucket_info(0).atom_size());
+        ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
         EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
                   data.bucket_info(0).start_bucket_elapsed_nanos());
         EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index f1b6029..e320419 100644
--- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -45,7 +45,7 @@
     countMetric->set_what(crashMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+        util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     auto metric_activation1 = config.add_metric_activation();
@@ -79,7 +79,7 @@
     countMetric->set_what(crashMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+        util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     auto metric_activation1 = config.add_metric_activation();
@@ -117,7 +117,7 @@
     countMetric->set_what(crashMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+        util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     auto metric_activation1 = config.add_metric_activation();
@@ -153,7 +153,7 @@
     countMetric->set_what(crashMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+        util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     auto metric_activation1 = config.add_metric_activation();
@@ -194,7 +194,7 @@
     countMetric->set_what(crashMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+        util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     int64_t metricId2 = 234567;
@@ -203,7 +203,7 @@
     countMetric->set_what(foregroundMatcher.id());
     countMetric->set_bucket(FIVE_MINUTES);
     countMetric->mutable_dimensions_in_what()->set_field(
-        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+        util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
 
     auto metric_activation1 = config.add_metric_activation();
@@ -236,7 +236,7 @@
 TEST(MetricActivationE2eTest, TestCountMetric) {
     auto config = CreateStatsdConfig();
 
-    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10;  // 10 secs
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -252,24 +252,25 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
 
@@ -277,7 +278,7 @@
     EXPECT_FALSE(metricProducer->mIsActive);
     // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
     // triggered by screen on event (tracker index 2).
-    EXPECT_EQ(eventActivationMap.size(), 2u);
+    ASSERT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -289,19 +290,19 @@
 
     std::unique_ptr<LogEvent> event;
 
-    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -311,13 +312,12 @@
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // First processed event.
-    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -329,8 +329,8 @@
 
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
-    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -343,17 +343,17 @@
     EXPECT_EQ(broadcastCount, 1);
 
     // 3rd processed event.
-    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
-    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -362,13 +362,13 @@
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // Re-activate metric via screen on.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10,
+                                          android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -378,55 +378,53 @@
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // 4th processed event.
-    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+                           ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(4, reports.reports(0).metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(4, countMetrics.data_size());
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(4, countMetrics.data_size());
 
     auto data = countMetrics.data(0);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -434,23 +432,22 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
-    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
-              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 }
 
 TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) {
     auto config = CreateStatsdConfigWithOneDeactivation();
 
-    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10;  // 10 secs
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -466,24 +463,25 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
@@ -492,7 +490,7 @@
     EXPECT_FALSE(metricProducer->mIsActive);
     // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
     // triggered by screen on event (tracker index 2).
-    EXPECT_EQ(eventActivationMap.size(), 2u);
+    ASSERT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -501,26 +499,26 @@
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
-    EXPECT_EQ(eventDeactivationMap.size(), 1u);
+    ASSERT_EQ(eventDeactivationMap.size(), 1u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
-    EXPECT_EQ(eventDeactivationMap[3].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[3].size(), 1u);
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     std::unique_ptr<LogEvent> event;
 
-    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -531,13 +529,12 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // First processed event.
-    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -550,8 +547,8 @@
 
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
-    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -565,17 +562,17 @@
     EXPECT_EQ(broadcastCount, 1);
 
     // 3rd processed event.
-    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
-    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -585,13 +582,13 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // Re-activate metric via screen on.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10,
+                                          android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -602,16 +599,16 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // 4th processed event.
-    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
@@ -622,16 +619,16 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // 5th processed event.
-    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode activation.
-    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
@@ -642,13 +639,13 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // Screen-on activation expired.
-    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -657,16 +654,16 @@
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
-    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 5);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
@@ -677,12 +674,12 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     // Cancel battery saver mode activation.
-    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 6);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -694,49 +691,47 @@
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+                           ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(5, countMetrics.data_size());
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
 
     auto data = countMetrics.data(0);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -744,12 +739,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -757,12 +752,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(4);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -773,7 +768,7 @@
 TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) {
     auto config = CreateStatsdConfigWithTwoDeactivations();
 
-    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10;  // 10 secs
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -789,24 +784,25 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
@@ -815,7 +811,7 @@
     EXPECT_FALSE(metricProducer->mIsActive);
     // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
     // triggered by screen on event (tracker index 2).
-    EXPECT_EQ(eventActivationMap.size(), 2u);
+    ASSERT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -824,29 +820,29 @@
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
-    EXPECT_EQ(eventDeactivationMap.size(), 2u);
+    ASSERT_EQ(eventDeactivationMap.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
     EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end());
-    EXPECT_EQ(eventDeactivationMap[3].size(), 1u);
-    EXPECT_EQ(eventDeactivationMap[4].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[3].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[4].size(), 1u);
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     std::unique_ptr<LogEvent> event;
 
-    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -858,13 +854,12 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // First processed event.
-    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -878,8 +873,8 @@
 
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
-    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -894,17 +889,17 @@
     EXPECT_EQ(broadcastCount, 1);
 
     // 3rd processed event.
-    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
-    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -915,13 +910,13 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // Re-activate metric via screen on.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10,
+                                          android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -933,16 +928,16 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // 4th processed event.
-    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
@@ -954,17 +949,17 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // 5th processed event.
-    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode and screen on activation.
-    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -975,12 +970,12 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // Screen-on activation expired.
-    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -990,16 +985,16 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
-    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 5);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
@@ -1011,12 +1006,12 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     // Cancel battery saver mode and screen on activation.
-    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 6);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
@@ -1029,49 +1024,47 @@
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+                           ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(5, countMetrics.data_size());
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
 
     auto data = countMetrics.data(0);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1079,12 +1072,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1092,12 +1085,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(4);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1108,7 +1101,7 @@
 TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) {
     auto config = CreateStatsdConfigWithSameDeactivations();
 
-    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10;  // 10 secs
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -1124,24 +1117,25 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
@@ -1150,7 +1144,7 @@
     EXPECT_FALSE(metricProducer->mIsActive);
     // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
     // triggered by screen on event (tracker index 2).
-    EXPECT_EQ(eventActivationMap.size(), 2u);
+    ASSERT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -1159,9 +1153,9 @@
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
-    EXPECT_EQ(eventDeactivationMap.size(), 1u);
+    ASSERT_EQ(eventDeactivationMap.size(), 1u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
-    EXPECT_EQ(eventDeactivationMap[3].size(), 2u);
+    ASSERT_EQ(eventDeactivationMap[3].size(), 2u);
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[3][1], eventActivationMap[2]);
     EXPECT_EQ(broadcastCount, 0);
@@ -1169,28 +1163,28 @@
     std::unique_ptr<LogEvent> event;
 
     // Event that should be ignored.
-    event = CreateAppCrashEvent(111, bucketStartTimeNs + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 1, 111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 1);
 
     // Activate metric via screen on for 2 minutes.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10);
 
     // 1st processed event.
-    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Enable battery saver mode activation for 5 minutes.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -1200,112 +1194,111 @@
     EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10);
 
     // 2nd processed event.
-    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 + 40);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 40, 333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 40);
 
     // Cancel battery saver mode and screen on activation.
     int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61;
-    event = CreateScreenBrightnessChangedEvent(64, firstDeactivation);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(firstDeactivation, 64);
+    processor.OnLogEvent(event.get(), firstDeactivation);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
 
     // Should be ignored
-    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 61 + 80);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 61 + 80, 444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 61 + 80);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
 
     // 3rd processed event.
-    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80, 555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80);
 
     // Cancel battery saver mode activation.
     int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13;
-    event = CreateScreenBrightnessChangedEvent(140, secondDeactivation);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(secondDeactivation, 140);
+    processor.OnLogEvent(event.get(), secondDeactivation);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
 
     // Should be ignored.
-    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80, 666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80);
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+                           ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(3, countMetrics.data_size());
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
 
     auto data = countMetrics.data(0);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(555, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(secondDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos());
 }
 
 TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) {
     auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations();
 
-    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10;  // 10 secs
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
@@ -1321,24 +1314,25 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
-    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
-            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+    StatsLogProcessor processor(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
+            [](const ConfigKey& key) { return true; },
             [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
-                    const vector<int64_t>& activeConfigs) {
+                                                             const vector<int64_t>& activeConfigs) {
                 broadcastCount++;
                 EXPECT_EQ(broadcastUid, uid);
                 activeConfigsBroadcast.clear();
-                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
-                        activeConfigs.begin(), activeConfigs.end());
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
+                                              activeConfigs.end());
                 return true;
             });
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
-    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor.mMetricsManagers.size(), 1u);
     sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
     EXPECT_TRUE(metricsManager->isConfigValid());
-    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 2);
+    ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 2);
     sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
     auto& eventActivationMap = metricProducer->mEventActivationMap;
     auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
@@ -1351,7 +1345,7 @@
     EXPECT_FALSE(metricProducer2->mIsActive);
     // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
     // triggered by screen on event (tracker index 2).
-    EXPECT_EQ(eventActivationMap.size(), 2u);
+    ASSERT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -1360,15 +1354,15 @@
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
-    EXPECT_EQ(eventDeactivationMap.size(), 2u);
+    ASSERT_EQ(eventDeactivationMap.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
     EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end());
-    EXPECT_EQ(eventDeactivationMap[3].size(), 1u);
-    EXPECT_EQ(eventDeactivationMap[4].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[3].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[4].size(), 1u);
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
-    EXPECT_EQ(eventActivationMap2.size(), 2u);
+    ASSERT_EQ(eventActivationMap2.size(), 2u);
     EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end());
     EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end());
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
@@ -1377,20 +1371,20 @@
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap2[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
-    EXPECT_EQ(eventDeactivationMap2.size(), 2u);
+    ASSERT_EQ(eventDeactivationMap2.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end());
     EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end());
-    EXPECT_EQ(eventDeactivationMap[3].size(), 1u);
-    EXPECT_EQ(eventDeactivationMap[4].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[3].size(), 1u);
+    ASSERT_EQ(eventDeactivationMap[4].size(), 1u);
     EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     std::unique_ptr<LogEvent> event;
 
-    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(1111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + 5, 1111);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_FALSE(metricProducer2->mIsActive);
@@ -1398,10 +1392,10 @@
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 1);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1423,15 +1417,14 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // First processed event.
-    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(2222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + 15, 2222);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1454,10 +1447,10 @@
 
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
-    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(3333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 3333);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1481,20 +1474,20 @@
     EXPECT_EQ(broadcastCount, 1);
 
     // 3rd processed event.
-    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(4444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 4444);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
-    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(5555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 5555);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
@@ -1515,12 +1508,12 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // Re-activate metric via screen on.
-    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10,
+                                          android::view::DISPLAY_STATE_ON);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
@@ -1542,17 +1535,17 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // 4th processed event.
-    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(6666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 6666);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 3);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1574,18 +1567,18 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // 5th processed event.
-    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(7777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 7777);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode and screen on activation.
-    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_FALSE(metricsManager->isActive());
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
@@ -1606,13 +1599,13 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // Screen-on activation expired.
-    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(8888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 8888);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 4);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
@@ -1632,17 +1625,17 @@
     EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
-    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
-    event = CreateMoveToForegroundEvent(9999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
+    event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 9999);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 5);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1664,11 +1657,11 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     // Cancel battery saver mode and screen on activation.
-    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140);
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 6);
-    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    ASSERT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
     EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
@@ -1691,51 +1684,48 @@
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
     processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+                           ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(2, reports.reports(0).metrics_size());
-    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
-    EXPECT_EQ(5, reports.reports(0).metrics(1).count_metrics().data_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(2, reports.reports(0).metrics_size());
 
     StatsLogReport::CountMetricDataWrapper countMetrics;
 
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
-    EXPECT_EQ(5, countMetrics.data_size());
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
 
     auto data = countMetrics.data(0);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1743,12 +1733,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1756,53 +1746,51 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(4);
-    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
-
-   countMetrics.clear_data();
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(1).count_metrics(), &countMetrics);
-    EXPECT_EQ(5, countMetrics.data_size());
+    countMetrics.clear_data();
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(1).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
 
     data = countMetrics.data(0);
-    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(1);
-    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(2);
-    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     // Partial bucket as metric is deactivated.
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1810,12 +1798,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(3);
-    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1823,12 +1811,12 @@
               data.bucket_info(0).end_bucket_elapsed_nanos());
 
     data = countMetrics.data(4);
-    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* uid field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
-    EXPECT_EQ(1, data.bucket_info_size());
+    ASSERT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(1, data.bucket_info(0).count());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               data.bucket_info(0).start_bucket_elapsed_nanos());
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 78fb391..5e77ee0 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -48,12 +48,12 @@
     auto isSyncingPredicate = CreateIsSyncingPredicate();
     auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
     *syncDimension = CreateAttributionUidDimensions(
-        android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+        util::SYNC_STATE_CHANGED, {Position::FIRST});
     syncDimension->add_child()->set_field(2 /* name field*/);
 
     auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
     *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
-        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+        CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
 
     *config.add_predicate() = screenIsOffPredicate;
     *config.add_predicate() = isSyncingPredicate;
@@ -72,26 +72,26 @@
     countMetric->set_condition(combinationPredicate->id());
     // The metric is dimensioning by uid only.
     *countMetric->mutable_dimensions_in_what() =
-        CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1});
+        CreateDimensions(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1});
     countMetric->set_bucket(FIVE_MINUTES);
 
     // Links between crash atom and condition of app is in syncing.
     auto links = countMetric->add_links();
     links->set_condition(isSyncingPredicate.id());
     auto dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
     *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+            util::SYNC_STATE_CHANGED, {Position::FIRST});
 
     // Links between crash atom and condition of app is in background.
     links = countMetric->add_links();
     links->set_condition(isInBackgroundPredicate.id());
     dimensionWhat = links->mutable_fields_in_what();
-    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
     auto dimensionCondition = links->mutable_fields_in_condition();
-    dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     dimensionCondition->add_child()->set_field(1);  // uid field.
     return config;
 }
@@ -103,56 +103,54 @@
     auto config = CreateStatsdConfig();
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
 
     ConfigKey cfgKey;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
     int appUid = 123;
-    auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1);
-    auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201);
-    auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101);
+    auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid);
+    auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid);
+    auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid);
 
-    auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51);
-    auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299);
-    auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001);
+    auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid);
+    auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid);
+    auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid);
 
-    auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16);
-    auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249);
+    auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid);
+    auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid);
 
-    auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351);
-    auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2);
+    auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid);
+    auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid);
 
-    auto screenTurnedOnEvent =
-        CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-                                      bucketStartTimeNs + 2);
-    auto screenTurnedOffEvent =
-        CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-                                      bucketStartTimeNs + 200);
+    auto screenTurnedOnEvent = CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    auto screenTurnedOffEvent = CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     auto screenTurnedOnEvent2 =
-        CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-                                      bucketStartTimeNs + 2 * bucketSizeNs - 100);
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON);
 
-    std::vector<AttributionNodeInternal> attributions = {
-            CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
-    auto syncOnEvent1 =
-        CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
-    auto syncOffEvent1 =
-        CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
-    auto syncOnEvent2 =
-        CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
+    std::vector<int> attributionUids = {appUid, appUid + 1};
+    std::vector<string> attributionTags = {"App1", "GMSCoreModule1"};
 
-    auto moveToBackgroundEvent1 =
-        CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
+    auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids,
+                                             attributionTags, "ReadEmail");
+    auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids,
+                                            attributionTags, "ReadEmail");
+    auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000,
+                                             attributionUids, attributionTags, "ReadDoc");
+
+    auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid);
     auto moveToForegroundEvent1 =
-        CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250);
+            CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid);
 
     auto moveToBackgroundEvent2 =
-        CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350);
+            CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid);
     auto moveToForegroundEvent2 =
-        CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1);
+            CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid);
 
     /*
                     bucket #1                               bucket #2
@@ -199,22 +197,22 @@
     }
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
     EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
     auto data = reports.reports(0).metrics(0).count_metrics().data(0);
     // Validate dimension value.
-    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
     // Uid field.
     EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
     EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid);
@@ -227,50 +225,51 @@
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
 
     int appUid = 123;
-    auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1);
-    auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201);
-    auto crashEvent3 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101);
+    auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid);
+    auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid);
+    auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid);
 
-    auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51);
-    auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299);
-    auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001);
+    auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid);
+    auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid);
+    auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid);
 
-    auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16);
-    auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249);
+    auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid);
+    auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid);
 
-    auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351);
-    auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2);
+    auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid);
+    auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid);
 
     auto screenTurnedOnEvent = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 2);
+            bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     auto screenTurnedOffEvent = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200);
+            bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     auto screenTurnedOnEvent2 =
-            CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + 2 * bucketSizeNs - 100);
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON);
 
-    std::vector<AttributionNodeInternal> attributions = {
-            CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
-    auto syncOnEvent1 = CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
-    auto syncOffEvent1 =
-            CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
-    auto syncOnEvent2 =
-            CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
+    std::vector<int> attributionUids = {appUid, appUid + 1};
+    std::vector<string> attributionTags = {"App1", "GMSCoreModule1"};
 
-    auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
+    auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids,
+                                             attributionTags, "ReadEmail");
+    auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids,
+                                            attributionTags, "ReadEmail");
+    auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000,
+                                             attributionUids, attributionTags, "ReadDoc");
+
+    auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid);
     auto moveToForegroundEvent1 =
-            CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250);
+            CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid);
 
     auto moveToBackgroundEvent2 =
-            CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350);
+            CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid);
     auto moveToForegroundEvent2 =
-            CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1);
+            CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid);
 
     /*
                     bucket #1                               bucket #2
@@ -318,24 +317,23 @@
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
 
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2);
     EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
     EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3);
     auto data = reports.reports(0).metrics(0).count_metrics().data(0);
     // Validate dimension value.
-    EXPECT_EQ(data.dimensions_in_what().field(),
-              android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
-    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
     // Uid field.
     EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
     EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid);
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 0bc3ebb..c03b925 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -12,45 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android/binder_ibinder.h>
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
-#include <binder/IPCThreadState.h>
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
 #include "src/StatsService.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
+using::ndk::SharedRefBase;
+using std::shared_ptr;
 
 namespace android {
 namespace os {
 namespace statsd {
 
 #ifdef __ANDROID__
-
-const string kAndroid = "android";
+namespace {
 const string kApp1 = "app1.sharing.1";
 const int kConfigKey = 789130123;  // Randomly chosen to avoid collisions with existing configs.
+const int kCallingUid = 0; // Randomly chosen
 
-void SendConfig(StatsService& service, const StatsdConfig& config) {
+void SendConfig(shared_ptr<StatsService>& service, const StatsdConfig& config) {
     string str;
     config.SerializeToString(&str);
     std::vector<uint8_t> configAsVec(str.begin(), str.end());
-    bool success;
-    service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str()));
+    service->addConfiguration(kConfigKey, configAsVec, kCallingUid);
 }
 
 ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
                                bool include_current = false) {
     vector<uint8_t> output;
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), kConfigKey);
+    ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey);
     processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/,
                             true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output);
     ConfigMetricsReportList reports;
     reports.ParseFromArray(output.data(), output.size());
     EXPECT_EQ(1, reports.reports_size());
-    return reports.reports(0);
+    return reports.reports(kCallingUid);
 }
 
 StatsdConfig MakeConfig() {
@@ -69,9 +71,10 @@
 StatsdConfig MakeValueMetricConfig(int64_t minTime) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto pulledAtomMatcher =
-            CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE);
+            CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
     *config.add_atom_matcher() = pulledAtomMatcher;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
@@ -80,21 +83,23 @@
     valueMetric->set_id(123456);
     valueMetric->set_what(pulledAtomMatcher.id());
     *valueMetric->mutable_value_field() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
     *valueMetric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
     valueMetric->set_bucket(FIVE_MINUTES);
     valueMetric->set_min_bucket_size_nanos(minTime);
     valueMetric->set_use_absolute_value_on_reset(true);
+    valueMetric->set_skip_zero_diff_output(false);
     return config;
 }
 
 StatsdConfig MakeGaugeMetricConfig(int64_t minTime) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto pulledAtomMatcher =
-                CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE);
+                CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
     *config.add_atom_matcher() = pulledAtomMatcher;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
@@ -104,135 +109,192 @@
     gaugeMetric->set_what(pulledAtomMatcher.id());
     gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true);
     *gaugeMetric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
     gaugeMetric->set_bucket(FIVE_MINUTES);
     gaugeMetric->set_min_bucket_size_nanos(minTime);
     return config;
 }
+}  // anonymous namespace
 
 TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 2, 100).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 3);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 3);
     // Expect no metrics since the bucket has not finished yet.
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(0, report.metrics(0).count_metrics().data_size());
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get());
     // This is a new installation, so there shouldn't be a split (should be same as the without
     // split case).
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
-                               String16(""));
+    service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(0, report.metrics(0).count_metrics().data_size());
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
-                               {String16("")});
+    service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                                {String16("")});
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
-                               String16(""));
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get());
+    service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
     ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
     ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
-    EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
-                    has_start_bucket_elapsed_nanos());
-    EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
-                    has_end_bucket_elapsed_nanos());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_start_bucket_elapsed_nanos());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_end_bucket_elapsed_nanos());
     EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
 }
 
 TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
-                               {String16("")});
+    service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                                {String16("")});
 
     // Force the uidmap to update at timestamp 2.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get());
+    service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
     // Goes into the second bucket.
-    service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get());
 
-    ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
     ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
     ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
-    EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
-                    has_start_bucket_elapsed_nanos());
-    EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
-                    has_end_bucket_elapsed_nanos());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_start_bucket_elapsed_nanos());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_end_bucket_elapsed_nanos());
+    EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
+}
+
+TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    SendConfig(service, MakeConfig());
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    // Goes into the first bucket
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get());
+    int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+    // Goes into the second bucket.
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get());
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC);
+    backfillStartEndTimestamp(&report);
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_start_bucket_elapsed_nanos());
+    EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+              report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos());
     EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
 }
 
 TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
-                               String16("v2"), String16(""));
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
+    service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size());
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
+    backfillStartEndTimestamp(&report);
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(0, report.metrics(0).value_metrics().skipped_size());
+
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)),
+              report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos());
 }
 
 TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+    const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
@@ -241,41 +303,86 @@
     // Can't test the start time since it will be based on the actual time when the pulling occurs.
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
               report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    // Initial pull will fail since puller is not registered.
+    SendConfig(service, MakeValueMetricConfig(0));
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+    int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+
+    // First bucket is dropped due to the initial pull failing
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size());
+    EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+              report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(
+            MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+            report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
                                String16("v2"), String16(""));
 
-    ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
-    StatsService service(nullptr, nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
-    service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
-                               String16(""));
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+    service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
     backfillStartEndTimestamp(&report);
     ASSERT_EQ(1, report.metrics_size());
     ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
@@ -283,6 +390,38 @@
     EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos());
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
               report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos());
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    // Initial pull will fail since puller hasn't been registered.
+    SendConfig(service, MakeGaugeMetricConfig(0));
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+    int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    // No data in the first bucket, so nothing is reported
+    ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(
+            MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+            report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index fb878dc7..4d39282 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android/binder_interface_utils.h>
 #include <gtest/gtest.h>
 
 #include "src/StatsLogProcessor.h"
@@ -20,6 +21,8 @@
 
 #include <vector>
 
+using ::ndk::SharedRefBase;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -33,8 +36,9 @@
 StatsdConfig CreateStatsdConfig(bool useCondition = true) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
     auto pulledAtomMatcher =
-            CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE);
+            CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
     *config.add_atom_matcher() = pulledAtomMatcher;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
@@ -49,9 +53,9 @@
         valueMetric->set_condition(screenIsOffPredicate.id());
     }
     *valueMetric->mutable_value_field() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
     *valueMetric->mutable_dimensions_in_what() =
-            CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
     valueMetric->set_bucket(FIVE_MINUTES);
     valueMetric->set_use_absolute_value_on_reset(true);
     valueMetric->set_skip_zero_diff_output(false);
@@ -59,45 +63,181 @@
     return config;
 }
 
+StatsdConfig CreateStatsdConfigWithStates() {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
+
+    auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
+    *config.add_atom_matcher() = pulledAtomMatcher;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+    auto screenOnPredicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = screenOnPredicate;
+
+    auto screenOffPredicate = CreateScreenIsOffPredicate();
+    *config.add_predicate() = screenOffPredicate;
+
+    auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+    *config.add_predicate() = deviceUnpluggedPredicate;
+
+    auto screenOnOnBatteryPredicate = config.add_predicate();
+    screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+    screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+    addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+    auto screenOffOnBatteryPredicate = config.add_predicate();
+    screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery"));
+    screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate);
+    addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate);
+
+    const State screenState =
+            CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123);
+    *config.add_state() = screenState;
+
+    // ValueMetricSubsystemSleepWhileScreenOnOnBattery
+    auto valueMetric1 = config.add_value_metric();
+    valueMetric1->set_id(metricId);
+    valueMetric1->set_what(pulledAtomMatcher.id());
+    valueMetric1->set_condition(screenOnOnBatteryPredicate->id());
+    *valueMetric1->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    valueMetric1->set_bucket(FIVE_MINUTES);
+    valueMetric1->set_use_absolute_value_on_reset(true);
+    valueMetric1->set_skip_zero_diff_output(false);
+    valueMetric1->set_max_pull_delay_sec(INT_MAX);
+
+    // ValueMetricSubsystemSleepWhileScreenOffOnBattery
+    ValueMetric* valueMetric2 = config.add_value_metric();
+    valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery"));
+    valueMetric2->set_what(pulledAtomMatcher.id());
+    valueMetric2->set_condition(screenOffOnBatteryPredicate->id());
+    *valueMetric2->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    valueMetric2->set_bucket(FIVE_MINUTES);
+    valueMetric2->set_use_absolute_value_on_reset(true);
+    valueMetric2->set_skip_zero_diff_output(false);
+    valueMetric2->set_max_pull_delay_sec(INT_MAX);
+
+    // ValueMetricSubsystemSleepWhileOnBatterySliceScreen
+    ValueMetric* valueMetric3 = config.add_value_metric();
+    valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen"));
+    valueMetric3->set_what(pulledAtomMatcher.id());
+    valueMetric3->set_condition(deviceUnpluggedPredicate.id());
+    *valueMetric3->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    valueMetric3->add_slice_by_state(screenState.id());
+    valueMetric3->set_bucket(FIVE_MINUTES);
+    valueMetric3->set_use_absolute_value_on_reset(true);
+    valueMetric3->set_skip_zero_diff_output(false);
+    valueMetric3->set_max_pull_delay_sec(INT_MAX);
+    return config;
+}
+
 }  // namespace
 
+/**
+ * Tests the initial condition and condition after the first log events for
+ * value metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(ValueMetricE2eTest, TestInitialConditionChanges) {
+    StatsdConfig config = CreateStatsdConfigWithStates();
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    int32_t tagId = util::SUBSYSTEM_SLEEP_STATE;
+    auto processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), tagId);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(3, metricsManager->mAllMetricProducers.size());
+
+    // Combination condition metric - screen on and device unplugged
+    sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+    // Simple condition metric - device unplugged
+    sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[2];
+
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON);
+    processor->OnLogEvent(screenOnEvent.get());
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF);
+    processor->OnLogEvent(screenOffEvent.get());
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+    auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+            configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+    processor->OnLogEvent(pluggedUsbEvent.get());
+    EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+    auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+            configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+    processor->OnLogEvent(pluggedNoneEvent.get());
+    EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+    EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
 TEST(ValueMetricE2eTest, TestPulledEvents) {
     auto config = CreateStatsdConfig();
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
 
     // When creating the config, the value metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& expectedPullTimeNs =
             processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs;
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
 
-    auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                        configAddedTimeNs + 55);
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
-    auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + 65);
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + 75);
+    screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     // Pulling alarm arrives on time and reset the sequential pulling alarm.
@@ -106,16 +246,16 @@
 
     processor->informPullAlarmFired(expectedPullTimeNs + 1);
 
-    screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + 2 * bucketSizeNs + 15);
+    screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 2 * bucketSizeNs + 15,
+                                                  android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
     processor->informPullAlarmFired(expectedPullTimeNs + 1);
 
     processor->informPullAlarmFired(expectedPullTimeNs + 1);
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + 4 * bucketSizeNs + 11);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 11,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     processor->informPullAlarmFired(expectedPullTimeNs + 1);
@@ -131,37 +271,36 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::ValueMetricDataWrapper valueMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
-    EXPECT_GT((int)valueMetrics.data_size(), 1);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
+    ASSERT_GT((int)valueMetrics.data_size(), 1);
 
     auto data = valueMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
     // We have 4 buckets, the first one was incomplete since the condition was unknown.
-    EXPECT_EQ(4, data.bucket_info_size());
+    ASSERT_EQ(4, data.bucket_info_size());
 
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(0).values_size());
+    ASSERT_EQ(1, data.bucket_info(0).values_size());
 
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(1).values_size());
+    ASSERT_EQ(1, data.bucket_info(1).values_size());
 
     EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(2).values_size());
+    ASSERT_EQ(1, data.bucket_info(2).values_size());
 
     EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(3).values_size());
+    ASSERT_EQ(1, data.bucket_info(3).values_size());
 }
 
 TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
@@ -169,23 +308,24 @@
     int64_t baseTimeNs = getElapsedRealtimeNs();
     // 10 mins == 2 bucket durations.
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
 
     // When creating the config, the value metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& expectedPullTimeNs =
@@ -193,16 +333,16 @@
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
 
     // Screen off/on/off events.
-    auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                        configAddedTimeNs + 55);
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
-    auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + 65);
+    auto screenOnEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + 75);
+    screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     // Pulling alarm arrives late by 2 buckets and 1 ns. 2 buckets late is too far away in the
@@ -211,8 +351,8 @@
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs);
 
     // This screen state change will start a new bucket.
-    screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
-                                                       configAddedTimeNs + 4 * bucketSizeNs + 65);
+    screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 65,
+                                                  android::view::DISPLAY_STATE_ON);
     processor->OnLogEvent(screenOnEvent.get());
 
     // The alarm is delayed but we already created a bucket thanks to the screen state condition.
@@ -220,8 +360,8 @@
     processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs);
 
-    screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
-                                                   configAddedTimeNs + 6 * bucketSizeNs + 31);
+    screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 6 * bucketSizeNs + 31,
+                                                   android::view::DISPLAY_STATE_OFF);
     processor->OnLogEvent(screenOffEvent.get());
 
     processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21);
@@ -239,44 +379,42 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::ValueMetricDataWrapper valueMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
-    EXPECT_GT((int)valueMetrics.data_size(), 1);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
+    ASSERT_GT((int)valueMetrics.data_size(), 1);
 
     auto data = valueMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
-    EXPECT_EQ(3, data.bucket_info_size());
+    ASSERT_EQ(3, data.bucket_info_size());
 
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(0).values_size());
+    ASSERT_EQ(1, data.bucket_info(0).values_size());
 
     EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(1).values_size());
+    ASSERT_EQ(1, data.bucket_info(1).values_size());
 
     EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 10 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, data.bucket_info(2).values_size());
+    ASSERT_EQ(1, data.bucket_info(2).values_size());
 }
 
 TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
     auto config = CreateStatsdConfig(false);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
-    int64_t bucketSizeNs =
-        TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
 
     auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = batterySaverStartMatcher;
-    const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets.
+    const int64_t ttlNs = 2 * bucketSizeNs;  // Two buckets.
     auto metric_activation = config.add_metric_activation();
     metric_activation->set_metric_id(metricId);
     metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY);
@@ -285,20 +423,22 @@
     event_activation->set_ttl_seconds(ttlNs / 1000000000);
 
     ConfigKey cfgKey;
-    auto processor = CreateStatsLogProcessor(
-        baseTimeNs, configAddedTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()->second->
-            mAllMetricProducers[0]->getCurrentBucketNum();
+    int startBucketNum = processor->mMetricsManagers.begin()
+                                 ->second->mAllMetricProducers[0]
+                                 ->getCurrentBucketNum();
     EXPECT_GT(startBucketNum, (int64_t)0);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // When creating the config, the value metric producer should register the alarm at the
     // end of the current bucket.
-    EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
+    ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size());
     EXPECT_EQ(bucketSizeNs,
               processor->mPullerManager->mReceivers.begin()->second.front().intervalNs);
     int64_t& expectedPullTimeNs =
@@ -306,24 +446,24 @@
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
 
     // Pulling alarm arrives on time and reset the sequential pulling alarm.
-    processor->informPullAlarmFired(expectedPullTimeNs + 1); // 15 mins + 1 ns.
+    processor->informPullAlarmFired(expectedPullTimeNs + 1);  // 15 mins + 1 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // Activate the metric. A pull occurs here
-    const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis.
+    const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000);  // 2 millis.
     auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs);
-    processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms.
+    processor->OnLogEvent(batterySaverOnEvent.get());  // 15 mins + 2 ms.
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
-    processor->informPullAlarmFired(expectedPullTimeNs + 1); // 20 mins + 1 ns.
+    processor->informPullAlarmFired(expectedPullTimeNs + 1);  // 20 mins + 1 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, expectedPullTimeNs);
 
-    processor->informPullAlarmFired(expectedPullTimeNs + 2); // 25 mins + 2 ns.
+    processor->informPullAlarmFired(expectedPullTimeNs + 2);  // 25 mins + 2 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs);
 
     // Create random event to deactivate metric.
-    auto deactivationEvent = CreateScreenBrightnessChangedEvent(50, activationNs + ttlNs + 1);
+    auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50);
     processor->OnLogEvent(deactivationEvent.get());
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
@@ -342,31 +482,192 @@
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(1, reports.reports_size());
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::ValueMetricDataWrapper valueMetrics;
-    sortMetricDataByDimensionsValue(
-            reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
-    EXPECT_GT((int)valueMetrics.data_size(), 0);
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
+    ASSERT_GT((int)valueMetrics.data_size(), 0);
 
     auto data = valueMetrics.data(0);
-    EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
-    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
     EXPECT_EQ(1 /* subsystem name field */,
               data.dimensions_in_what().value_tuple().dimensions_value(0).field());
     EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
     // We have 2 full buckets, the two surrounding the activation are dropped.
-    EXPECT_EQ(2, data.bucket_info_size());
+    ASSERT_EQ(2, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, bucketInfo.values_size());
+    ASSERT_EQ(1, bucketInfo.values_size());
 
     bucketInfo = data.bucket_info(1);
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
-    EXPECT_EQ(1, bucketInfo.values_size());
+    ASSERT_EQ(1, bucketInfo.values_size());
+}
+
+/**
+ * Test initialization of a simple value metric that is sliced by a state.
+ *
+ * ValueCpuUserTimePerScreenState
+ */
+TEST(ValueMetricE2eTest, TestInitWithSlicedState) {
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto pulledAtomMatcher =
+            CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
+    *config.add_atom_matcher() = pulledAtomMatcher;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create value metric that slices by screen state without a map.
+    int64_t metricId = 123456;
+    auto valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(pulledAtomMatcher.id());
+    *valueMetric->mutable_value_field() =
+            CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
+    valueMetric->add_slice_by_state(screenState.id());
+    valueMetric->set_max_pull_delay_sec(INT_MAX);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    // Check that ValueMetricProducer was initialized correctly.
+    ASSERT_EQ(1U, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0));
+    ASSERT_EQ(0, metricProducer->mStateGroupMap.size());
+}
+
+/**
+ * Test initialization of a value metric that is sliced by state and has
+ * dimensions_in_what.
+ *
+ * ValueCpuUserTimePerUidPerUidProcessState
+ */
+TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) {
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto cpuTimePerUidMatcher =
+            CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID);
+    *config.add_atom_matcher() = cpuTimePerUidMatcher;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create value metric that slices by screen state with a complete map.
+    int64_t metricId = 123456;
+    auto valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(cpuTimePerUidMatcher.id());
+    *valueMetric->mutable_value_field() =
+            CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
+    *valueMetric->mutable_dimensions_in_what() =
+            CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */});
+    valueMetric->add_slice_by_state(uidProcessState.id());
+    MetricStateLink* stateLink = valueMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+    valueMetric->set_max_pull_delay_sec(INT_MAX);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Check that ValueMetricProducer was initialized correctly.
+    ASSERT_EQ(1U, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0));
+    ASSERT_EQ(0, metricProducer->mStateGroupMap.size());
+}
+
+/**
+ * Test initialization of a value metric that is sliced by state and has
+ * dimensions_in_what.
+ *
+ * ValueCpuUserTimePerUidPerUidProcessState
+ */
+TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) {
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto cpuTimePerUidMatcher =
+            CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID);
+    *config.add_atom_matcher() = cpuTimePerUidMatcher;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create value metric that slices by screen state with a complete map.
+    int64_t metricId = 123456;
+    auto valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(cpuTimePerUidMatcher.id());
+    *valueMetric->mutable_value_field() =
+            CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
+    valueMetric->add_slice_by_state(uidProcessState.id());
+    MetricStateLink* stateLink = valueMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+    valueMetric->set_max_pull_delay_sec(INT_MAX);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // No StateTrackers are initialized.
+    EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount());
+
+    // Config initialization fails.
+    ASSERT_EQ(0, processor->mMetricsManagers.size());
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index e13bf14..52bc222 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -42,7 +42,7 @@
     auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
     // The predicate is dimensioning by any attribution node and both by uid and tag.
     FieldMatcher dimensions = CreateAttributionUidAndTagDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
     // Also slice by the wakelock tag
     dimensions.add_child()->set_field(3);  // The wakelock tag is set in field 3 of the wakelock.
     *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
@@ -56,18 +56,16 @@
     // The metric is dimensioning by first attribution node and only by uid.
     *durationMetric->mutable_dimensions_in_what() =
         CreateAttributionUidDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
     durationMetric->set_bucket(FIVE_MINUTES);
     return config;
 }
 
-std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
-                                                      CreateAttribution(222, "GMSCoreModule1"),
-                                                      CreateAttribution(222, "GMSCoreModule2")};
+std::vector<int> attributionUids1 = {111, 222, 222};
+std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1", "GMSCoreModule2"};
 
-std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"),
-                                                      CreateAttribution(222, "GMSCoreModule1"),
-                                                      CreateAttribution(222, "GMSCoreModule2")};
+std::vector<int> attributionUids2 = {111, 222, 222};
+std::vector<string> attributionTags2 = {"App2", "GMSCoreModule1", "GMSCoreModule2"};
 
 /*
 Events:
@@ -81,20 +79,21 @@
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
 
     auto screenTurnedOnEvent = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1);
+            bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     auto screenTurnedOffEvent = CreateScreenStateChangedEvent(
-            android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200);
+            bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
     auto screenTurnedOnEvent2 =
-            CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-                                          bucketStartTimeNs + bucketSizeNs + 500);
+            CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 500,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON);
 
-    auto acquireEvent1 = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
-    auto releaseEvent1 =
-            CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
-    auto acquireEvent2 =
-            CreateAcquireWakelockEvent(attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
-    auto releaseEvent2 = CreateReleaseWakelockEvent(attributions2, "wl2",
-                                                    bucketStartTimeNs + 2 * bucketSizeNs - 15);
+    auto acquireEvent1 = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                                    attributionTags1, "wl1");
+    auto releaseEvent1 = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2,
+                                                    attributionUids1, attributionTags1, "wl1");
+    auto acquireEvent2 = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 10,
+                                                    attributionUids2, attributionTags2, "wl2");
+    auto releaseEvent2 = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 15,
+                                                    attributionUids2, attributionTags2, "wl2");
 
     std::vector<std::unique_ptr<LogEvent>> events;
 
@@ -122,30 +121,30 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     vector<uint8_t> buffer;
     ConfigMetricsReportList reports;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
 
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
     // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
 
     auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
+                                    util::WAKELOCK_STATE_CHANGED, 111);
     // Validate bucket info.
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
     data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // The wakelock holding interval starts from the screen off event and to the end of the 1st
     // bucket.
@@ -159,27 +158,27 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     vector<uint8_t> buffer;
     ConfigMetricsReportList reports;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
     // Dump the report after the end of 2nd bucket.
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
     auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
+                                    util::WAKELOCK_STATE_CHANGED, 111);
     // Two output buckets.
     // The wakelock holding interval in the 1st bucket starts from the screen off event and to
     // the end of the 1st bucket.
@@ -189,6 +188,7 @@
     // ends at the second screen on event.
     EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
 }
+
 TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) {
     ConfigKey cfgKey;
     auto config = CreateStatsdConfig(DurationMetric::SUM);
@@ -196,7 +196,7 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     vector<uint8_t> buffer;
@@ -204,31 +204,31 @@
 
     std::vector<std::unique_ptr<LogEvent>> events;
     events.push_back(
-            CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-                                          bucketStartTimeNs + 2 * bucketSizeNs + 90));
-    events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
-                                                bucketStartTimeNs + 2 * bucketSizeNs + 100));
-    events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
-                                                bucketStartTimeNs + 5 * bucketSizeNs + 100));
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100,
+                                                attributionUids1, attributionTags1, "wl3"));
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100,
+                                                attributionUids1, attributionTags1, "wl3"));
     sortLogEventsByTimestamp(&events);
     for (const auto& event : events) {
         processor->OnLogEvent(event.get());
     }
 
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
     auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
+                                    util::WAKELOCK_STATE_CHANGED, 111);
     // The last wakelock holding spans 4 buckets.
     EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
     EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
@@ -243,13 +243,13 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
 
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
@@ -257,13 +257,13 @@
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
 
-    EXPECT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
 
     // When using ProtoOutputStream, if nothing written to a sub msg, it won't be treated as
     // one. It was previsouly 1 because we had a fake onDumpReport which calls add_metric() by
     // itself.
-    EXPECT_EQ(1, reports.reports(0).metrics_size());
-    EXPECT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size());
 }
 
 TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) {
@@ -273,27 +273,27 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
     // Dump the report after the end of 2nd bucket. One dimension with one bucket.
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
     auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
+                                    util::WAKELOCK_STATE_CHANGED, 111);
     // The max is acquire event for wl1 to screen off start.
     EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200);
 }
@@ -305,7 +305,7 @@
     uint64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
     auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
-    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    ASSERT_EQ(processor->mMetricsManagers.size(), 1u);
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     FeedEvents(config, processor);
     ConfigMetricsReportList reports;
@@ -313,31 +313,31 @@
 
     std::vector<std::unique_ptr<LogEvent>> events;
     events.push_back(
-            CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-                                          bucketStartTimeNs + 2 * bucketSizeNs + 90));
-    events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
-                                                bucketStartTimeNs + 2 * bucketSizeNs + 100));
-    events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
-                                                bucketStartTimeNs + 5 * bucketSizeNs + 100));
+            CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100,
+                                                attributionUids1, attributionTags1, "wl3"));
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100,
+                                                attributionUids1, attributionTags1, "wl3"));
     sortLogEventsByTimestamp(&events);
     for (const auto& event : events) {
         processor->OnLogEvent(event.get());
     }
 
-    processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
     backfillStringInReport(&reports);
     backfillStartEndTimestamp(&reports);
-    EXPECT_EQ(reports.reports_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
-    EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+    ASSERT_EQ(reports.reports_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
     auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
     ValidateAttributionUidDimension(data.dimensions_in_what(),
-                                    android::util::WAKELOCK_STATE_CHANGED, 111);
+                                    util::WAKELOCK_STATE_CHANGED, 111);
     // The last wakelock holding spans 4 buckets.
     EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs);
     EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_elapsed_nanos(),
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
deleted file mode 100644
index 3240918..0000000
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "GpuStatsPuller_test"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <graphicsenv/GpuStatsInfo.h>
-#include <log/log.h>
-
-#include "src/external/GpuStatsPuller.h"
-#include "statslog.h"
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// clang-format off
-static const std::string DRIVER_PACKAGE_NAME      = "TEST_DRIVER";
-static const std::string DRIVER_VERSION_NAME      = "TEST_DRIVER_VERSION";
-static const std::string APP_PACKAGE_NAME         = "TEST_APP";
-static const int64_t TIMESTAMP_WALLCLOCK          = 111;
-static const int64_t TIMESTAMP_ELAPSED            = 222;
-static const int64_t DRIVER_VERSION_CODE          = 333;
-static const int64_t DRIVER_BUILD_TIME            = 444;
-static const int64_t GL_LOADING_COUNT             = 3;
-static const int64_t GL_LOADING_FAILURE_COUNT     = 1;
-static const int64_t VK_LOADING_COUNT             = 4;
-static const int64_t VK_LOADING_FAILURE_COUNT     = 0;
-static const int64_t ANGLE_LOADING_COUNT          = 2;
-static const int64_t ANGLE_LOADING_FAILURE_COUNT  = 1;
-static const int64_t GL_DRIVER_LOADING_TIME_0     = 555;
-static const int64_t GL_DRIVER_LOADING_TIME_1     = 666;
-static const int64_t VK_DRIVER_LOADING_TIME_0     = 777;
-static const int64_t VK_DRIVER_LOADING_TIME_1     = 888;
-static const int64_t VK_DRIVER_LOADING_TIME_2     = 999;
-static const int64_t ANGLE_DRIVER_LOADING_TIME_0  = 1010;
-static const int64_t ANGLE_DRIVER_LOADING_TIME_1  = 1111;
-static const int32_t VULKAN_VERSION               = 1;
-static const int32_t CPU_VULKAN_VERSION           = 2;
-static const int32_t GLES_VERSION                 = 3;
-static const bool CPU_VULKAN_IN_USE               = true;
-static const size_t NUMBER_OF_VALUES_GLOBAL       = 13;
-static const size_t NUMBER_OF_VALUES_APP          = 6;
-// clang-format on
-
-class MockGpuStatsPuller : public GpuStatsPuller {
-public:
-    MockGpuStatsPuller(const int tagId, vector<std::shared_ptr<LogEvent>>* data)
-        : GpuStatsPuller(tagId), mData(data){};
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override {
-        *data = *mData;
-        return true;
-    }
-
-    vector<std::shared_ptr<LogEvent>>* mData;
-};
-
-class GpuStatsPuller_test : public ::testing::Test {
-public:
-    GpuStatsPuller_test() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    ~GpuStatsPuller_test() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-};
-
-TEST_F(GpuStatsPuller_test, PullGpuStatsGlobalInfo) {
-    vector<std::shared_ptr<LogEvent>> inData, outData;
-    std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_GLOBAL_INFO,
-                                                            TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED);
-    EXPECT_TRUE(event->write(DRIVER_PACKAGE_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_CODE));
-    EXPECT_TRUE(event->write(DRIVER_BUILD_TIME));
-    EXPECT_TRUE(event->write(GL_LOADING_COUNT));
-    EXPECT_TRUE(event->write(GL_LOADING_FAILURE_COUNT));
-    EXPECT_TRUE(event->write(VK_LOADING_COUNT));
-    EXPECT_TRUE(event->write(VK_LOADING_FAILURE_COUNT));
-    EXPECT_TRUE(event->write(VULKAN_VERSION));
-    EXPECT_TRUE(event->write(CPU_VULKAN_VERSION));
-    EXPECT_TRUE(event->write(GLES_VERSION));
-    EXPECT_TRUE(event->write(ANGLE_LOADING_COUNT));
-    EXPECT_TRUE(event->write(ANGLE_LOADING_FAILURE_COUNT));
-    event->init();
-    inData.emplace_back(event);
-    MockGpuStatsPuller mockPuller(android::util::GPU_STATS_GLOBAL_INFO, &inData);
-    mockPuller.ForceClearCache();
-    mockPuller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::GPU_STATS_GLOBAL_INFO, outData[0]->GetTagId());
-    ASSERT_EQ(NUMBER_OF_VALUES_GLOBAL, outData[0]->size());
-    EXPECT_EQ(DRIVER_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_NAME, outData[0]->getValues()[1].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[2].mValue.long_value);
-    EXPECT_EQ(DRIVER_BUILD_TIME, outData[0]->getValues()[3].mValue.long_value);
-    EXPECT_EQ(GL_LOADING_COUNT, outData[0]->getValues()[4].mValue.long_value);
-    EXPECT_EQ(GL_LOADING_FAILURE_COUNT, outData[0]->getValues()[5].mValue.long_value);
-    EXPECT_EQ(VK_LOADING_COUNT, outData[0]->getValues()[6].mValue.long_value);
-    EXPECT_EQ(VK_LOADING_FAILURE_COUNT, outData[0]->getValues()[7].mValue.long_value);
-    EXPECT_EQ(VULKAN_VERSION, outData[0]->getValues()[8].mValue.int_value);
-    EXPECT_EQ(CPU_VULKAN_VERSION, outData[0]->getValues()[9].mValue.int_value);
-    EXPECT_EQ(GLES_VERSION, outData[0]->getValues()[10].mValue.int_value);
-    EXPECT_EQ(ANGLE_LOADING_COUNT, outData[0]->getValues()[11].mValue.long_value);
-    EXPECT_EQ(ANGLE_LOADING_FAILURE_COUNT, outData[0]->getValues()[12].mValue.long_value);
-}
-
-TEST_F(GpuStatsPuller_test, PullGpuStatsAppInfo) {
-    vector<std::shared_ptr<LogEvent>> inData, outData;
-    std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_APP_INFO,
-                                                            TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED);
-    EXPECT_TRUE(event->write(APP_PACKAGE_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_CODE));
-    std::vector<int64_t> glDriverLoadingTime;
-    glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_0);
-    glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_1);
-    std::vector<int64_t> vkDriverLoadingTime;
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_0);
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_1);
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_2);
-    std::vector<int64_t> angleDriverLoadingTime;
-    angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_0);
-    angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_1);
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(glDriverLoadingTime)));
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime)));
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime)));
-    EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE));
-    event->init();
-    inData.emplace_back(event);
-    MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData);
-    mockPuller.ForceClearCache();
-    mockPuller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::GPU_STATS_APP_INFO, outData[0]->GetTagId());
-    ASSERT_EQ(NUMBER_OF_VALUES_APP, outData[0]->size());
-    EXPECT_EQ(APP_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[1].mValue.long_value);
-    EXPECT_EQ(int64VectorToProtoByteString(glDriverLoadingTime),
-              outData[0]->getValues()[2].mValue.str_value);
-    EXPECT_EQ(int64VectorToProtoByteString(vkDriverLoadingTime),
-              outData[0]->getValues()[3].mValue.str_value);
-    EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime),
-              outData[0]->getValues()[4].mValue.str_value);
-    EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
deleted file mode 100644
index 38bc194..0000000
--- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <android/os/IncidentReportArgs.h>
-
-#include <gtest/gtest.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-TEST(IncidentReportArgsTest, testSerialization) {
-    IncidentReportArgs args;
-    args.setAll(0);
-    args.addSection(1000);
-    args.addSection(1001);
-
-    vector<uint8_t> header1;
-    header1.push_back(0x1);
-    header1.push_back(0x2);
-    vector<uint8_t> header2;
-    header1.push_back(0x22);
-    header1.push_back(0x33);
-
-    args.addHeader(header1);
-    args.addHeader(header2);
-
-    args.setPrivacyPolicy(1);
-
-    args.setReceiverPkg("com.android.os");
-    args.setReceiverCls("com.android.os.Receiver");
-
-    Parcel out;
-    status_t err = args.writeToParcel(&out);
-    EXPECT_EQ(NO_ERROR, err);
-
-    out.setDataPosition(0);
-
-    IncidentReportArgs args2;
-    err = args2.readFromParcel(&out);
-    EXPECT_EQ(NO_ERROR, err);
-
-    EXPECT_EQ(0, args2.all());
-    set<int> sections;
-    sections.insert(1000);
-    sections.insert(1001);
-    EXPECT_EQ(sections, args2.sections());
-    EXPECT_EQ(1, args2.getPrivacyPolicy());
-
-    EXPECT_EQ(string("com.android.os"), args2.receiverPkg());
-    EXPECT_EQ(string("com.android.os.Receiver"), args2.receiverCls());
-
-    vector<vector<uint8_t>> headers;
-    headers.push_back(header1);
-    headers.push_back(header2);
-    EXPECT_EQ(headers, args2.headers());
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
new file mode 100644
index 0000000..85a6088
--- /dev/null
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -0,0 +1,219 @@
+// Copyright (C) 2019 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 "src/external/StatsCallbackPuller.h"
+
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
+#include <android/binder_interface_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <chrono>
+#include <thread>
+#include <vector>
+
+#include "../metrics/metrics_test_helper.h"
+#include "src/stats_log_util.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using namespace testing;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomCallback;
+using aidl::android::os::IPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using std::this_thread::sleep_for;
+using testing::Contains;
+
+namespace {
+int pullTagId = -12;
+bool pullSuccess;
+vector<int64_t> values;
+int64_t pullDelayNs;
+int64_t pullTimeoutNs;
+int64_t pullCoolDownNs;
+std::thread pullThread;
+
+AStatsEvent* createSimpleEvent(int64_t value) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, pullTagId);
+    AStatsEvent_writeInt64(event, value);
+    AStatsEvent_build(event);
+    return event;
+}
+
+void executePull(const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
+    // Convert stats_events into StatsEventParcels.
+    vector<StatsEventParcel> parcels;
+    for (int i = 0; i < values.size(); i++) {
+        AStatsEvent* event = createSimpleEvent(values[i]);
+        size_t size;
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
+
+        StatsEventParcel p;
+        // vector.assign() creates a copy, but this is inevitable unless
+        // stats_event.h/c uses a vector as opposed to a buffer.
+        p.buffer.assign(buffer, buffer + size);
+        parcels.push_back(std::move(p));
+        AStatsEvent_release(event);
+    }
+
+    sleep_for(std::chrono::nanoseconds(pullDelayNs));
+    resultReceiver->pullFinished(pullTagId, pullSuccess, parcels);
+}
+
+class FakePullAtomCallback : public BnPullAtomCallback {
+public:
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
+        // Force pull to happen in separate thread to simulate binder.
+        pullThread = std::thread(executePull, resultReceiver);
+        return Status::ok();
+    }
+};
+
+class StatsCallbackPullerTest : public ::testing::Test {
+public:
+    StatsCallbackPullerTest() {
+    }
+
+    void SetUp() override {
+        pullSuccess = false;
+        pullDelayNs = 0;
+        values.clear();
+        pullTimeoutNs = 10000000000LL;  // 10 seconds.
+        pullCoolDownNs = 1000000000;    // 1 second.
+    }
+
+    void TearDown() override {
+        if (pullThread.joinable()) {
+            pullThread.join();
+        }
+        values.clear();
+    }
+};
+}  // Anonymous namespace.
+
+TEST_F(StatsCallbackPullerTest, PullSuccess) {
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
+    int64_t value = 43;
+    pullSuccess = true;
+    values.push_back(value);
+
+    StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
+
+    vector<std::shared_ptr<LogEvent>> dataHolder;
+    int64_t startTimeNs = getElapsedRealtimeNs();
+    EXPECT_TRUE(puller.PullInternal(&dataHolder));
+    int64_t endTimeNs = getElapsedRealtimeNs();
+
+    ASSERT_EQ(1, dataHolder.size());
+    EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
+    EXPECT_LT(startTimeNs, dataHolder[0]->GetElapsedTimestampNs());
+    EXPECT_GT(endTimeNs, dataHolder[0]->GetElapsedTimestampNs());
+    ASSERT_EQ(1, dataHolder[0]->size());
+    EXPECT_EQ(value, dataHolder[0]->getValues()[0].mValue.int_value);
+}
+
+TEST_F(StatsCallbackPullerTest, PullFail) {
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
+    pullSuccess = false;
+    int64_t value = 1234;
+    values.push_back(value);
+
+    StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
+
+    vector<shared_ptr<LogEvent>> dataHolder;
+    EXPECT_FALSE(puller.PullInternal(&dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
+}
+
+TEST_F(StatsCallbackPullerTest, PullTimeout) {
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
+    pullSuccess = true;
+    pullDelayNs = MillisToNano(5);  // 5ms.
+    pullTimeoutNs = 10000;    // 10 microseconds.
+    int64_t value = 4321;
+    values.push_back(value);
+
+    StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
+
+    vector<shared_ptr<LogEvent>> dataHolder;
+    int64_t startTimeNs = getElapsedRealtimeNs();
+    // Returns true to let StatsPuller code evaluate the timeout.
+    EXPECT_TRUE(puller.PullInternal(&dataHolder));
+    int64_t endTimeNs = getElapsedRealtimeNs();
+    int64_t actualPullDurationNs = endTimeNs - startTimeNs;
+
+    // Pull should take at least the timeout amount of time, but should stop early because the delay
+    // is bigger.
+    EXPECT_LT(pullTimeoutNs, actualPullDurationNs);
+    EXPECT_GT(pullDelayNs, actualPullDurationNs);
+    ASSERT_EQ(0, dataHolder.size());
+
+    // Let the pull return and make sure that the dataHolder is not modified.
+    pullThread.join();
+    ASSERT_EQ(0, dataHolder.size());
+}
+
+// Register a puller and ensure that the timeout logic works.
+TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) {
+    shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
+    pullSuccess = true;
+    pullDelayNs = MillisToNano(5);  // 5 ms.
+    pullTimeoutNs = 10000;    // 10 microsseconds.
+    int64_t value = 4321;
+    int32_t uid = 123;
+    values.push_back(value);
+
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
+                                            vector<int32_t>(), cb);
+    vector<shared_ptr<LogEvent>> dataHolder;
+    int64_t startTimeNs = getElapsedRealtimeNs();
+    // Returns false, since StatsPuller code will evaluate the timeout.
+    EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, startTimeNs, &dataHolder));
+    int64_t endTimeNs = getElapsedRealtimeNs();
+    int64_t actualPullDurationNs = endTimeNs - startTimeNs;
+
+    // Pull should take at least the timeout amount of time, but should stop early because the delay
+    // is bigger. Make sure that the time is closer to the timeout, than to the intended delay.
+    EXPECT_LT(pullTimeoutNs, actualPullDurationNs);
+    EXPECT_GT(pullDelayNs / 5, actualPullDurationNs);
+    ASSERT_EQ(0, dataHolder.size());
+
+    // Let the pull return and make sure that the dataHolder is not modified.
+    pullThread.join();
+    ASSERT_EQ(0, dataHolder.size());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/external/StatsPullerManager_test.cpp b/cmds/statsd/tests/external/StatsPullerManager_test.cpp
new file mode 100644
index 0000000..c76e85e
--- /dev/null
+++ b/cmds/statsd/tests/external/StatsPullerManager_test.cpp
@@ -0,0 +1,150 @@
+// Copyright (C) 2020 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 "src/external/StatsPullerManager.h"
+
+#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+int pullTagId1 = 10101;
+int pullTagId2 = 10102;
+int uid1 = 9999;
+int uid2 = 8888;
+ConfigKey configKey(50, 12345);
+ConfigKey badConfigKey(60, 54321);
+int unregisteredUid = 98765;
+int64_t coolDownNs = NS_PER_SEC;
+int64_t timeoutNs = NS_PER_SEC / 2;
+
+AStatsEvent* createSimpleEvent(int32_t atomId, int32_t value) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, atomId);
+    AStatsEvent_writeInt32(event, value);
+    AStatsEvent_build(event);
+    return event;
+}
+
+class FakePullAtomCallback : public BnPullAtomCallback {
+public:
+    FakePullAtomCallback(int32_t uid) : mUid(uid){};
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
+        vector<StatsEventParcel> parcels;
+        AStatsEvent* event = createSimpleEvent(atomTag, mUid);
+        size_t size;
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
+
+        StatsEventParcel p;
+        // vector.assign() creates a copy, but this is inevitable unless
+        // stats_event.h/c uses a vector as opposed to a buffer.
+        p.buffer.assign(buffer, buffer + size);
+        parcels.push_back(std::move(p));
+        AStatsEvent_release(event);
+        resultReceiver->pullFinished(atomTag, /*success*/ true, parcels);
+        return Status::ok();
+    }
+    int32_t mUid;
+};
+
+class FakePullUidProvider : public PullUidProvider {
+public:
+    vector<int32_t> getPullAtomUids(int atomId) override {
+        if (atomId == pullTagId1) {
+            return {uid2, uid1};
+        } else if (atomId == pullTagId2) {
+            return {uid2};
+        }
+        return {};
+    }
+};
+
+sp<StatsPullerManager> createPullerManagerAndRegister() {
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    shared_ptr<FakePullAtomCallback> cb1 = SharedRefBase::make<FakePullAtomCallback>(uid1);
+    pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1, true);
+    shared_ptr<FakePullAtomCallback> cb2 = SharedRefBase::make<FakePullAtomCallback>(uid2);
+    pullerManager->RegisterPullAtomCallback(uid2, pullTagId1, coolDownNs, timeoutNs, {}, cb2, true);
+    pullerManager->RegisterPullAtomCallback(uid1, pullTagId2, coolDownNs, timeoutNs, {}, cb1, true);
+    return pullerManager;
+}
+}  // anonymous namespace
+
+TEST(StatsPullerManagerTest, TestPullInvalidUid) {
+    sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
+
+    vector<shared_ptr<LogEvent>> data;
+    EXPECT_FALSE(pullerManager->Pull(pullTagId1, {unregisteredUid}, /*timestamp =*/1, &data, true));
+}
+
+TEST(StatsPullerManagerTest, TestPullChoosesCorrectUid) {
+    sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
+
+    vector<shared_ptr<LogEvent>> data;
+    EXPECT_TRUE(pullerManager->Pull(pullTagId1, {uid1}, /*timestamp =*/1, &data, true));
+    ASSERT_EQ(data.size(), 1);
+    EXPECT_EQ(data[0]->GetTagId(), pullTagId1);
+    ASSERT_EQ(data[0]->getValues().size(), 1);
+    EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid1);
+}
+
+TEST(StatsPullerManagerTest, TestPullInvalidConfigKey) {
+    sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
+    sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
+    pullerManager->RegisterPullUidProvider(configKey, uidProvider);
+
+    vector<shared_ptr<LogEvent>> data;
+    EXPECT_FALSE(pullerManager->Pull(pullTagId1, badConfigKey, /*timestamp =*/1, &data, true));
+}
+
+TEST(StatsPullerManagerTest, TestPullConfigKeyGood) {
+    sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
+    sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
+    pullerManager->RegisterPullUidProvider(configKey, uidProvider);
+
+    vector<shared_ptr<LogEvent>> data;
+    EXPECT_TRUE(pullerManager->Pull(pullTagId1, configKey, /*timestamp =*/1, &data, true));
+    EXPECT_EQ(data[0]->GetTagId(), pullTagId1);
+    ASSERT_EQ(data[0]->getValues().size(), 1);
+    EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid2);
+}
+
+TEST(StatsPullerManagerTest, TestPullConfigKeyNoPullerWithUid) {
+    sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
+    sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
+    pullerManager->RegisterPullUidProvider(configKey, uidProvider);
+
+    vector<shared_ptr<LogEvent>> data;
+    EXPECT_FALSE(pullerManager->Pull(pullTagId2, configKey, /*timestamp =*/1, &data, true));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp
index 76e2097..55a9036 100644
--- a/cmds/statsd/tests/external/StatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsPuller_test.cpp
@@ -15,11 +15,14 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+
 #include <chrono>
 #include <thread>
 #include <vector>
+
 #include "../metrics/metrics_test_helper.h"
 #include "src/stats_log_util.h"
+#include "stats_event.h"
 #include "tests/statsd_test_util.h"
 
 #ifdef __ANDROID__
@@ -35,7 +38,7 @@
 using std::this_thread::sleep_for;
 using testing::Contains;
 
-// cooldown time 1sec.
+namespace {
 int pullTagId = 10014;
 
 bool pullSuccess;
@@ -44,7 +47,8 @@
 
 class FakePuller : public StatsPuller {
 public:
-    FakePuller() : StatsPuller(pullTagId){};
+    FakePuller()
+        : StatsPuller(pullTagId, /*coolDownNs=*/MillisToNano(10), /*timeoutNs=*/MillisToNano(5)){};
 
 private:
     bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override {
@@ -56,11 +60,15 @@
 
 FakePuller puller;
 
-shared_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) {
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(pullTagId, eventTimeNs);
-    event->write(value);
-    event->init();
-    return event;
+std::unique_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, pullTagId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+    AStatsEvent_writeInt64(statsEvent, value);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
 }
 
 class StatsPullerTest : public ::testing::Test {
@@ -76,31 +84,33 @@
     }
 };
 
-TEST_F(StatsPullerTest, PullSucces) {
+}  // Anonymous namespace.
+
+TEST_F(StatsPullerTest, PullSuccess) {
     pullData.push_back(createSimpleEvent(1111L, 33));
 
     pullSuccess = true;
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_TRUE(puller.Pull(&dataHolder));
-    EXPECT_EQ(1, dataHolder.size());
+    EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
     EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
     EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
-    EXPECT_EQ(1, dataHolder[0]->size());
+    ASSERT_EQ(1, dataHolder[0]->size());
     EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
 
-    sleep_for(std::chrono::seconds(1));
+    sleep_for(std::chrono::milliseconds(11));
 
     pullData.clear();
     pullData.push_back(createSimpleEvent(2222L, 44));
 
     pullSuccess = true;
 
-    EXPECT_TRUE(puller.Pull(&dataHolder));
-    EXPECT_EQ(1, dataHolder.size());
+    EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
     EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
     EXPECT_EQ(2222L, dataHolder[0]->GetElapsedTimestampNs());
-    EXPECT_EQ(1, dataHolder[0]->size());
+    ASSERT_EQ(1, dataHolder[0]->size());
     EXPECT_EQ(44, dataHolder[0]->getValues()[0].mValue.int_value);
 }
 
@@ -110,47 +120,49 @@
     pullSuccess = true;
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_TRUE(puller.Pull(&dataHolder));
-    EXPECT_EQ(1, dataHolder.size());
+    EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
     EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
     EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
-    EXPECT_EQ(1, dataHolder[0]->size());
+    ASSERT_EQ(1, dataHolder[0]->size());
     EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
 
-    sleep_for(std::chrono::seconds(1));
+    sleep_for(std::chrono::milliseconds(11));
 
     pullData.clear();
     pullData.push_back(createSimpleEvent(2222L, 44));
 
     pullSuccess = false;
     dataHolder.clear();
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 
+    // Fails due to hitting the cool down.
     pullSuccess = true;
     dataHolder.clear();
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 }
 
 // Test pull takes longer than timeout, 2nd pull happens shorter than cooldown
 TEST_F(StatsPullerTest, PullTakeTooLongAndPullFast) {
     pullData.push_back(createSimpleEvent(1111L, 33));
     pullSuccess = true;
-    // timeout is 0.5
-    pullDelayNs = (long)(0.8 * NS_PER_SEC);
+    // timeout is 5ms
+    pullDelayNs = MillisToNano(6);
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 
     pullData.clear();
     pullData.push_back(createSimpleEvent(2222L, 44));
+    pullDelayNs = 0;
 
     pullSuccess = true;
     dataHolder.clear();
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 }
 
 TEST_F(StatsPullerTest, PullFail) {
@@ -159,19 +171,19 @@
     pullSuccess = false;
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 }
 
 TEST_F(StatsPullerTest, PullTakeTooLong) {
     pullData.push_back(createSimpleEvent(1111L, 33));
 
     pullSuccess = true;
-    pullDelayNs = NS_PER_SEC;
+    pullDelayNs = MillisToNano(6);
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 }
 
 TEST_F(StatsPullerTest, PullTooFast) {
@@ -180,11 +192,11 @@
     pullSuccess = true;
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_TRUE(puller.Pull(&dataHolder));
-    EXPECT_EQ(1, dataHolder.size());
+    EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
     EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
     EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
-    EXPECT_EQ(1, dataHolder[0]->size());
+    ASSERT_EQ(1, dataHolder[0]->size());
     EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
 
     pullData.clear();
@@ -193,11 +205,11 @@
     pullSuccess = true;
 
     dataHolder.clear();
-    EXPECT_TRUE(puller.Pull(&dataHolder));
-    EXPECT_EQ(1, dataHolder.size());
+    EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
     EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
     EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
-    EXPECT_EQ(1, dataHolder[0]->size());
+    ASSERT_EQ(1, dataHolder[0]->size());
     EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
 }
 
@@ -207,16 +219,93 @@
     pullSuccess = false;
 
     vector<std::shared_ptr<LogEvent>> dataHolder;
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 
     pullData.clear();
     pullData.push_back(createSimpleEvent(2222L, 44));
 
     pullSuccess = true;
 
-    EXPECT_FALSE(puller.Pull(&dataHolder));
-    EXPECT_EQ(0, dataHolder.size());
+    EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
+}
+
+TEST_F(StatsPullerTest, PullSameEventTime) {
+    pullData.push_back(createSimpleEvent(1111L, 33));
+
+    pullSuccess = true;
+    int64_t eventTimeNs = getElapsedRealtimeNs();
+
+    vector<std::shared_ptr<LogEvent>> dataHolder;
+    EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
+    EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
+    EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
+    ASSERT_EQ(1, dataHolder[0]->size());
+    EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
+
+    pullData.clear();
+    pullData.push_back(createSimpleEvent(2222L, 44));
+
+    // Sleep to ensure the cool down expires.
+    sleep_for(std::chrono::milliseconds(11));
+    pullSuccess = true;
+
+    dataHolder.clear();
+    EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(1, dataHolder.size());
+    EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
+    EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs());
+    ASSERT_EQ(1, dataHolder[0]->size());
+    EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value);
+}
+
+// Test pull takes longer than timeout, 2nd pull happens at same event time
+TEST_F(StatsPullerTest, PullTakeTooLongAndPullSameEventTime) {
+    pullData.push_back(createSimpleEvent(1111L, 33));
+    pullSuccess = true;
+    int64_t eventTimeNs = getElapsedRealtimeNs();
+    // timeout is 5ms
+    pullDelayNs = MillisToNano(6);
+
+    vector<std::shared_ptr<LogEvent>> dataHolder;
+    EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
+
+    // Sleep to ensure the cool down expires. 6ms is taken by the delay, so only 5 is needed here.
+    sleep_for(std::chrono::milliseconds(5));
+
+    pullData.clear();
+    pullData.push_back(createSimpleEvent(2222L, 44));
+    pullDelayNs = 0;
+
+    pullSuccess = true;
+    dataHolder.clear();
+    EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
+}
+
+TEST_F(StatsPullerTest, PullFailsAndPullSameEventTime) {
+    pullData.push_back(createSimpleEvent(1111L, 33));
+
+    pullSuccess = false;
+    int64_t eventTimeNs = getElapsedRealtimeNs();
+
+    vector<std::shared_ptr<LogEvent>> dataHolder;
+    EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
+
+    // Sleep to ensure the cool down expires.
+    sleep_for(std::chrono::milliseconds(11));
+
+    pullData.clear();
+    pullData.push_back(createSimpleEvent(2222L, 44));
+
+    pullSuccess = true;
+
+    EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder));
+    ASSERT_EQ(0, dataHolder.size());
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 6730828..a21dc87 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -13,12 +13,18 @@
 // limitations under the License.
 
 #include "external/puller_util.h"
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+
 #include <vector>
-#include "statslog.h"
+
 #include "../metrics/metrics_test_helper.h"
+#include "FieldValue.h"
+#include "annotations.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 #ifdef __ANDROID__
 
@@ -27,239 +33,371 @@
 namespace statsd {
 
 using namespace testing;
-using std::make_shared;
 using std::shared_ptr;
 using std::vector;
-using testing::Contains;
 /*
  * Test merge isolated and host uid
  */
+namespace {
+const int uidAtomTagId = 100;
+const vector<int> additiveFields = {3};
+const int nonUidAtomTagId = 200;
+const int timestamp = 1234;
+const int isolatedUid1 = 30;
+const int isolatedUid2 = 40;
+const int isolatedNonAdditiveData = 32;
+const int isolatedAdditiveData = 31;
+const int hostUid = 20;
+const int hostNonAdditiveData = 22;
+const int hostAdditiveData = 21;
+const int attributionAtomTagId = 300;
 
-int uidAtomTagId = android::util::CPU_CLUSTER_TIME;
-int nonUidAtomTagId = android::util::SYSTEM_UPTIME;
-int timestamp = 1234;
-int isolatedUid = 30;
-int isolatedAdditiveData = 31;
-int isolatedNonAdditiveData = 32;
-int hostUid = 20;
-int hostAdditiveData = 21;
-int hostNonAdditiveData = 22;
-
-void extractIntoVector(vector<shared_ptr<LogEvent>> events,
-                      vector<vector<int>>& ret) {
-  ret.clear();
-  status_t err;
-  for (const auto& event : events) {
-    vector<int> vec;
-    vec.push_back(event->GetInt(1, &err));
-    vec.push_back(event->GetInt(2, &err));
-    vec.push_back(event->GetInt(3, &err));
-    ret.push_back(vec);
-  }
+sp<MockUidMap> makeMockUidMap() {
+    return makeMockUidMapForOneHost(hostUid, {isolatedUid1, isolatedUid2});
 }
 
-TEST(puller_util, MergeNoDimension) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  // 30->22->31
-  event->write(isolatedUid);
-  event->write(hostNonAdditiveData);
-  event->write(isolatedAdditiveData);
-  event->init();
-  inputData.push_back(event);
+}  // anonymous namespace
 
-  // 20->22->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(hostNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, MergeNoDimension) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->22->31
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData,
+                            isolatedAdditiveData),
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
-      .WillRepeatedly(Return(hostUid));
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
-      .WillRepeatedly(ReturnArg<0>());
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+            // 20->22->21
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData,
+                            hostAdditiveData),
+    };
 
-  vector<vector<int>> actual;
-  extractIntoVector(inputData, actual);
-  vector<int> expectedV1 = {20, 22, 52};
-  EXPECT_EQ(1, (int)actual.size());
-  EXPECT_THAT(actual, Contains(expectedV1));
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
+
+    ASSERT_EQ(1, (int)data.size());
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
 }
 
-TEST(puller_util, MergeWithDimension) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  // 30->32->31
-  event->write(isolatedUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(isolatedAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, MergeWithDimension) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->32->31
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData,
+                            isolatedAdditiveData),
 
-  // 20->32->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 20->32->21
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData,
+                            hostAdditiveData),
 
-  // 20->22->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(hostNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 20->22->21
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData,
+                            hostAdditiveData),
+    };
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
-      .WillRepeatedly(Return(hostUid));
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
-      .WillRepeatedly(ReturnArg<0>());
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
 
-  vector<vector<int>> actual;
-  extractIntoVector(inputData, actual);
-  vector<int> expectedV1 = {20, 22, 21};
-  vector<int> expectedV2 = {20, 32, 52};
-  EXPECT_EQ(2, (int)actual.size());
-  EXPECT_THAT(actual, Contains(expectedV1));
-  EXPECT_THAT(actual, Contains(expectedV2));
+    ASSERT_EQ(2, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value);
 }
 
-TEST(puller_util, NoMergeHostUidOnly) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  // 20->32->31
-  event->write(hostUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(isolatedAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, NoMergeHostUidOnly) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 20->32->31
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData,
+                            isolatedAdditiveData),
 
-  // 20->22->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(hostNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 20->22->21
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData,
+                            hostAdditiveData),
+    };
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
-      .WillRepeatedly(Return(hostUid));
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
-      .WillRepeatedly(ReturnArg<0>());
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
 
-  // 20->32->31
-  // 20->22->21
-  vector<vector<int>> actual;
-  extractIntoVector(inputData, actual);
-  vector<int> expectedV1 = {20, 32, 31};
-  vector<int> expectedV2 = {20, 22, 21};
-  EXPECT_EQ(2, (int)actual.size());
-  EXPECT_THAT(actual, Contains(expectedV1));
-  EXPECT_THAT(actual, Contains(expectedV2));
+    ASSERT_EQ(2, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value);
 }
 
-TEST(puller_util, IsolatedUidOnly) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  // 30->32->31
-  event->write(hostUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(isolatedAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, IsolatedUidOnly) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->32->31
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData,
+                            isolatedAdditiveData),
 
-  // 30->22->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(hostNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 30->22->21
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData,
+                            hostAdditiveData),
+    };
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
-      .WillRepeatedly(Return(hostUid));
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
-      .WillRepeatedly(ReturnArg<0>());
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
 
-  // 20->32->31
-  // 20->22->21
-  vector<vector<int>> actual;
-  extractIntoVector(inputData, actual);
-  vector<int> expectedV1 = {20, 32, 31};
-  vector<int> expectedV2 = {20, 22, 21};
-  EXPECT_EQ(2, (int)actual.size());
-  EXPECT_THAT(actual, Contains(expectedV1));
-  EXPECT_THAT(actual, Contains(expectedV2));
+    ASSERT_EQ(2, (int)data.size());
+
+    // 20->32->31
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+
+    // 20->22->21
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value);
 }
 
-TEST(puller_util, MultipleIsolatedUidToOneHostUid) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  // 30->32->31
-  event->write(isolatedUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(isolatedAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUid) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->32->31
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData,
+                            isolatedAdditiveData),
 
-  // 31->32->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(isolatedUid + 1);
-  event->write(isolatedNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 31->32->21
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid2, isolatedNonAdditiveData,
+                            hostAdditiveData),
 
-  // 20->32->21
-  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
-  event->write(hostUid);
-  event->write(isolatedNonAdditiveData);
-  event->write(hostAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 20->32->21
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData,
+                            hostAdditiveData),
+    };
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(Return(hostUid));
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
 
-  vector<vector<int>> actual;
-  extractIntoVector(inputData, actual);
-  vector<int> expectedV1 = {20, 32, 73};
-  EXPECT_EQ(1, (int)actual.size());
-  EXPECT_THAT(actual, Contains(expectedV1));
+    ASSERT_EQ(1, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(3, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData,
+              actualFieldValues->at(2).mValue.int_value);
 }
 
-TEST(puller_util, NoNeedToMerge) {
-  vector<shared_ptr<LogEvent>> inputData;
-  shared_ptr<LogEvent> event =
-      make_shared<LogEvent>(nonUidAtomTagId, timestamp);
-  // 32
-  event->write(isolatedNonAdditiveData);
-  event->init();
-  inputData.push_back(event);
+TEST(PullerUtilTest, NoNeedToMerge) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 32->31
+            CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, isolatedNonAdditiveData,
+                                   isolatedAdditiveData),
 
-  event = make_shared<LogEvent>(nonUidAtomTagId, timestamp);
-  // 22
-  event->write(hostNonAdditiveData);
-  event->init();
-  inputData.push_back(event);
+            // 22->21
+            CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, hostNonAdditiveData,
+                                   hostAdditiveData),
 
-  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
-  mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, nonUidAtomTagId);
+    };
 
-  EXPECT_EQ(2, (int)inputData.size());
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, nonUidAtomTagId, {} /*no additive fields*/);
+
+    ASSERT_EQ(2, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(2, actualFieldValues->size());
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(1).mValue.int_value);
+
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(2, actualFieldValues->size());
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(1).mValue.int_value);
+}
+
+TEST(PullerUtilTest, MergeNoDimensionAttributionChain) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->tag1->400->tag2->22->31
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400},
+                                    {"tag1", "tag2"}, hostNonAdditiveData, isolatedAdditiveData),
+
+            // 20->tag1->400->tag2->22->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400},
+                                    {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields);
+
+    ASSERT_EQ(1, (int)data.size());
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(PullerUtilTest, MergeWithDimensionAttributionChain) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 200->tag1->30->tag2->32->31
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, isolatedUid1},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData,
+                                    isolatedAdditiveData),
+
+            // 200->tag1->20->tag2->32->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData),
+
+            // 200->tag1->20->tag2->22->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid},
+                                    {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields);
+
+    ASSERT_EQ(2, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value);
+
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(PullerUtilTest, NoMergeHostUidOnlyAttributionChain) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 20->tag1->400->tag2->32->31
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData,
+                                    isolatedAdditiveData),
+
+            // 20->tag1->400->tag2->22->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400},
+                                    {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields);
+
+    ASSERT_EQ(2, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value);
+
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(PullerUtilTest, IsolatedUidOnlyAttributionChain) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->tag1->400->tag2->32->31
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData,
+                                    isolatedAdditiveData),
+
+            // 30->tag1->400->tag2->22->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400},
+                                    {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields);
+
+    ASSERT_EQ(2, (int)data.size());
+
+    // 20->tag1->400->tag2->32->31
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value);
+
+    // 20->tag1->400->tag2->22->21
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value);
+}
+
+TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUidAttributionChain) {
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->tag1->400->tag2->32->31
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData,
+                                    isolatedAdditiveData),
+
+            // 31->tag1->400->tag2->32->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid2, 400},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData),
+
+            // 20->tag1->400->tag2->32->21
+            makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400},
+                                    {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields);
+
+    ASSERT_EQ(1, (int)data.size());
+
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value);
+    EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData,
+              actualFieldValues->at(5).mValue.int_value);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 2a43d9b..428c46f 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 #include "src/guardrail/StatsdStats.h"
-#include "statslog.h"
+#include "statslog_statsdtest.h"
 #include "tests/statsd_test_util.h"
 
 #include <gtest/gtest.h>
@@ -42,7 +42,7 @@
     StatsdStatsReport report;
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport = report.config_stats(0);
     EXPECT_EQ(0, configReport.uid());
     EXPECT_EQ(12345, configReport.id());
@@ -69,7 +69,7 @@
     StatsdStatsReport report;
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport = report.config_stats(0);
     // The invalid config should be put into icebox with a deletion time.
     EXPECT_TRUE(configReport.has_deletion_time_sec());
@@ -89,7 +89,7 @@
     StatsdStatsReport report;
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport = report.config_stats(0);
     EXPECT_FALSE(configReport.has_deletion_time_sec());
 
@@ -97,7 +97,7 @@
     stats.dumpStats(&output, false);
     good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport2 = report.config_stats(0);
     EXPECT_TRUE(configReport2.has_deletion_time_sec());
 }
@@ -145,21 +145,21 @@
     StatsdStatsReport report;
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport = report.config_stats(0);
-    EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size());
-    EXPECT_EQ(1, configReport.data_drop_time_sec_size());
-    EXPECT_EQ(1, configReport.data_drop_bytes_size());
+    ASSERT_EQ(2, configReport.broadcast_sent_time_sec_size());
+    ASSERT_EQ(1, configReport.data_drop_time_sec_size());
+    ASSERT_EQ(1, configReport.data_drop_bytes_size());
     EXPECT_EQ(123, configReport.data_drop_bytes(0));
-    EXPECT_EQ(3, configReport.dump_report_time_sec_size());
-    EXPECT_EQ(3, configReport.dump_report_data_size_size());
-    EXPECT_EQ(2, configReport.activation_time_sec_size());
-    EXPECT_EQ(1, configReport.deactivation_time_sec_size());
-    EXPECT_EQ(1, configReport.annotation_size());
+    ASSERT_EQ(3, configReport.dump_report_time_sec_size());
+    ASSERT_EQ(3, configReport.dump_report_data_size_size());
+    ASSERT_EQ(2, configReport.activation_time_sec_size());
+    ASSERT_EQ(1, configReport.deactivation_time_sec_size());
+    ASSERT_EQ(1, configReport.annotation_size());
     EXPECT_EQ(123, configReport.annotation(0).field_int64());
     EXPECT_EQ(456, configReport.annotation(0).field_int32());
 
-    EXPECT_EQ(2, configReport.matcher_stats_size());
+    ASSERT_EQ(2, configReport.matcher_stats_size());
     // matcher1 is the first in the list
     if (configReport.matcher_stats(0).id() == StringToId("matcher1")) {
         EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
@@ -174,18 +174,18 @@
         EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id());
     }
 
-    EXPECT_EQ(2, configReport.alert_stats_size());
+    ASSERT_EQ(2, configReport.alert_stats_size());
     bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1");
     EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id());
     EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times());
     EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id());
     EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times());
 
-    EXPECT_EQ(1, configReport.condition_stats_size());
+    ASSERT_EQ(1, configReport.condition_stats_size());
     EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id());
     EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());
 
-    EXPECT_EQ(1, configReport.metric_stats_size());
+    ASSERT_EQ(1, configReport.metric_stats_size());
     EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id());
     EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts());
 
@@ -199,21 +199,21 @@
     stats.dumpStats(&output, false);
     good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
-    EXPECT_EQ(1, report.config_stats_size());
+    ASSERT_EQ(1, report.config_stats_size());
     const auto& configReport2 = report.config_stats(0);
-    EXPECT_EQ(1, configReport2.matcher_stats_size());
+    ASSERT_EQ(1, configReport2.matcher_stats_size());
     EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id());
     EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times());
 
-    EXPECT_EQ(1, configReport2.condition_stats_size());
+    ASSERT_EQ(1, configReport2.condition_stats_size());
     EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id());
     EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts());
 
-    EXPECT_EQ(1, configReport2.metric_stats_size());
+    ASSERT_EQ(1, configReport2.metric_stats_size());
     EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id());
     EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());
 
-    EXPECT_EQ(1, configReport2.alert_stats_size());
+    ASSERT_EQ(1, configReport2.alert_stats_size());
     EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id());
     EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times());
 }
@@ -222,11 +222,11 @@
     StatsdStats stats;
     time_t now = time(nullptr);
     // old event, we get it from the stats buffer. should be ignored.
-    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, 1000);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000);
 
-    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1);
-    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2);
-    stats.noteAtomLogged(android::util::APP_CRASH_OCCURRED, now + 3);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2);
+    stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3);
 
     vector<uint8_t> output;
     stats.dumpStats(&output, false);
@@ -234,15 +234,15 @@
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
 
-    EXPECT_EQ(2, report.atom_stats_size());
+    ASSERT_EQ(2, report.atom_stats_size());
     bool sensorAtomGood = false;
     bool dropboxAtomGood = false;
 
     for (const auto& atomStats : report.atom_stats()) {
-        if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 3) {
+        if (atomStats.tag() == util::SENSOR_STATE_CHANGED && atomStats.count() == 3) {
             sensorAtomGood = true;
         }
-        if (atomStats.tag() == android::util::APP_CRASH_OCCURRED && atomStats.count() == 1) {
+        if (atomStats.tag() == util::APP_CRASH_OCCURRED && atomStats.count() == 1) {
             dropboxAtomGood = true;
         }
     }
@@ -254,8 +254,8 @@
 TEST(StatsdStatsTest, TestNonPlatformAtomLog) {
     StatsdStats stats;
     time_t now = time(nullptr);
-    int newAtom1 = android::util::kMaxPushedAtomId + 1;
-    int newAtom2 = android::util::kMaxPushedAtomId + 2;
+    int newAtom1 = StatsdStats::kMaxPushedAtomId + 1;
+    int newAtom2 = StatsdStats::kMaxPushedAtomId + 2;
 
     stats.noteAtomLogged(newAtom1, now + 1);
     stats.noteAtomLogged(newAtom1, now + 2);
@@ -267,7 +267,7 @@
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
 
-    EXPECT_EQ(2, report.atom_stats_size());
+    ASSERT_EQ(2, report.atom_stats_size());
     bool newAtom1Good = false;
     bool newAtom2Good = false;
 
@@ -287,22 +287,27 @@
 TEST(StatsdStatsTest, TestPullAtomStats) {
     StatsdStats stats;
 
-    stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 3333L);
-    stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 2222L);
-    stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 4444L);
+    stats.updateMinPullIntervalSec(util::DISK_SPACE, 3333L);
+    stats.updateMinPullIntervalSec(util::DISK_SPACE, 2222L);
+    stats.updateMinPullIntervalSec(util::DISK_SPACE, 4444L);
 
-    stats.notePull(android::util::DISK_SPACE);
-    stats.notePullTime(android::util::DISK_SPACE, 1111L);
-    stats.notePullDelay(android::util::DISK_SPACE, 1111L);
-    stats.notePull(android::util::DISK_SPACE);
-    stats.notePullTime(android::util::DISK_SPACE, 3333L);
-    stats.notePullDelay(android::util::DISK_SPACE, 3335L);
-    stats.notePull(android::util::DISK_SPACE);
-    stats.notePullFromCache(android::util::DISK_SPACE);
-    stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true);
-    stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, false);
-    stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true);
-
+    stats.notePull(util::DISK_SPACE);
+    stats.notePullTime(util::DISK_SPACE, 1111L);
+    stats.notePullDelay(util::DISK_SPACE, 1111L);
+    stats.notePull(util::DISK_SPACE);
+    stats.notePullTime(util::DISK_SPACE, 3333L);
+    stats.notePullDelay(util::DISK_SPACE, 3335L);
+    stats.notePull(util::DISK_SPACE);
+    stats.notePullFromCache(util::DISK_SPACE);
+    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true);
+    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false);
+    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true);
+    stats.notePullBinderCallFailed(util::DISK_SPACE);
+    stats.notePullUidProviderNotFound(util::DISK_SPACE);
+    stats.notePullerNotFound(util::DISK_SPACE);
+    stats.notePullerNotFound(util::DISK_SPACE);
+    stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L);
+    stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L);
 
     vector<uint8_t> output;
     stats.dumpStats(&output, false);
@@ -310,9 +315,9 @@
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
 
-    EXPECT_EQ(1, report.pulled_atom_stats_size());
+    ASSERT_EQ(1, report.pulled_atom_stats_size());
 
-    EXPECT_EQ(android::util::DISK_SPACE, report.pulled_atom_stats(0).atom_id());
+    EXPECT_EQ(util::DISK_SPACE, report.pulled_atom_stats(0).atom_id());
     EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull());
     EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache());
     EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec());
@@ -322,6 +327,16 @@
     EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos());
     EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count());
     EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count());
+    EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed());
+    EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found());
+    EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found());
+    ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size());
+    EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis());
+    EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis());
+    EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0)
+            .pull_timeout_elapsed_millis());
+    EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1)
+            .pull_timeout_elapsed_millis());
 }
 
 TEST(StatsdStatsTest, TestAtomMetricsStats) {
@@ -342,7 +357,7 @@
     bool good = report.ParseFromArray(&output[0], output.size());
     EXPECT_TRUE(good);
 
-    EXPECT_EQ(2, report.atom_metric_stats().size());
+    ASSERT_EQ(2, report.atom_metric_stats().size());
 
     auto atomStats = report.atom_metric_stats(0);
     EXPECT_EQ(1000L, atomStats.metric_id());
@@ -401,11 +416,11 @@
     const auto& configStats = stats.mConfigStats[key];
 
     size_t maxCount = StatsdStats::kMaxTimestampCount;
-    EXPECT_EQ(maxCount, configStats->broadcast_sent_time_sec.size());
-    EXPECT_EQ(maxCount, configStats->data_drop_time_sec.size());
-    EXPECT_EQ(maxCount, configStats->dump_report_stats.size());
-    EXPECT_EQ(maxCount, configStats->activation_time_sec.size());
-    EXPECT_EQ(maxCount, configStats->deactivation_time_sec.size());
+    ASSERT_EQ(maxCount, configStats->broadcast_sent_time_sec.size());
+    ASSERT_EQ(maxCount, configStats->data_drop_time_sec.size());
+    ASSERT_EQ(maxCount, configStats->dump_report_stats.size());
+    ASSERT_EQ(maxCount, configStats->activation_time_sec.size());
+    ASSERT_EQ(maxCount, configStats->deactivation_time_sec.size());
 
     // the oldest timestamp is the second timestamp in history
     EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front());
@@ -435,13 +450,13 @@
     StatsdStatsReport report;
     EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
     const int maxCount = StatsdStats::kMaxSystemServerRestarts;
-    EXPECT_EQ(maxCount, (int)report.system_restart_sec_size());
+    ASSERT_EQ(maxCount, (int)report.system_restart_sec_size());
 
     stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1);
     output.clear();
     stats.dumpStats(&output, false);
     EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
-    EXPECT_EQ(maxCount, (int)report.system_restart_sec_size());
+    ASSERT_EQ(maxCount, (int)report.system_restart_sec_size());
     EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1));
 }
 
@@ -462,19 +477,19 @@
     StatsdStatsReport report;
     EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
 
-    EXPECT_EQ(2, report.activation_guardrail_stats_size());
+    ASSERT_EQ(2, report.activation_guardrail_stats_size());
     bool uid1Good = false;
     bool uid2Good = false;
     for (const auto& guardrailTimes : report.activation_guardrail_stats()) {
         if (uid1 == guardrailTimes.uid()) {
             uid1Good = true;
-            EXPECT_EQ(2, guardrailTimes.guardrail_met_sec_size());
+            ASSERT_EQ(2, guardrailTimes.guardrail_met_sec_size());
             EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0));
             EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1));
         } else if (uid2 == guardrailTimes.uid()) {
             int maxCount = StatsdStats::kMaxTimestampCount;
             uid2Good = true;
-            EXPECT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size());
+            ASSERT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size());
             for (int i = 0; i < maxCount; i++) {
                 EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i));
             }
@@ -486,6 +501,41 @@
     EXPECT_TRUE(uid2Good);
 }
 
+TEST(StatsdStatsTest, TestAtomErrorStats) {
+    StatsdStats stats;
+
+    int pushAtomTag = 100;
+    int pullAtomTag = 1000;
+    int numErrors = 10;
+
+    for (int i = 0; i < numErrors; i++) {
+        // We must call noteAtomLogged as well because only those pushed atoms
+        // that have been logged will have stats printed about them in the
+        // proto.
+        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+        stats.noteAtomError(pushAtomTag, /*pull=*/false);
+
+        stats.noteAtomError(pullAtomTag, /*pull=*/true);
+    }
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+    // Check error count = numErrors for push atom
+    ASSERT_EQ(1, report.atom_stats_size());
+    const auto& pushedAtomStats = report.atom_stats(0);
+    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+    EXPECT_EQ(numErrors, pushedAtomStats.error_count());
+
+    // Check error count = numErrors for pull atom
+    ASSERT_EQ(1, report.pulled_atom_stats_size());
+    const auto& pulledAtomStats = report.pulled_atom_stats(0);
+    EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id());
+    EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
index d6cd876..3a65456 100644
--- a/cmds/statsd/tests/indexed_priority_queue_test.cpp
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -44,23 +44,23 @@
     sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
     sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
 
-    EXPECT_EQ(0u, ipq.size());
+    ASSERT_EQ(0u, ipq.size());
     EXPECT_TRUE(ipq.empty());
 
     ipq.push(aa4);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_FALSE(ipq.empty());
 
     ipq.push(aa8);
-    EXPECT_EQ(2u, ipq.size());
+    ASSERT_EQ(2u, ipq.size());
     EXPECT_FALSE(ipq.empty());
 
     ipq.remove(aa4);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_FALSE(ipq.empty());
 
     ipq.remove(aa8);
-    EXPECT_EQ(0u, ipq.size());
+    ASSERT_EQ(0u, ipq.size());
     EXPECT_TRUE(ipq.empty());
 }
 
@@ -126,17 +126,17 @@
     sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4_a);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4_a));
     EXPECT_FALSE(ipq.contains(aa4_b));
 
     ipq.push(aa4_a);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4_a));
     EXPECT_FALSE(ipq.contains(aa4_b));
 
     ipq.push(aa4_b);
-    EXPECT_EQ(2u, ipq.size());
+    ASSERT_EQ(2u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4_a));
     EXPECT_TRUE(ipq.contains(aa4_b));
 }
@@ -150,7 +150,7 @@
 
     ipq.push(aa4);
     ipq.remove(aa5);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4));
     EXPECT_FALSE(ipq.contains(aa5));
 }
@@ -164,17 +164,17 @@
 
     ipq.push(aa4_a);
     ipq.push(aa4_b);
-    EXPECT_EQ(2u, ipq.size());
+    ASSERT_EQ(2u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4_a));
     EXPECT_TRUE(ipq.contains(aa4_b));
 
     ipq.remove(aa4_b);
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_TRUE(ipq.contains(aa4_a));
     EXPECT_FALSE(ipq.contains(aa4_b));
 
     ipq.remove(aa4_a);
-    EXPECT_EQ(0u, ipq.size());
+    ASSERT_EQ(0u, ipq.size());
     EXPECT_FALSE(ipq.contains(aa4_a));
     EXPECT_FALSE(ipq.contains(aa4_b));
 }
@@ -205,22 +205,22 @@
     ipq.push(c);
     ipq.push(b);
     ipq.push(a);
-    EXPECT_EQ(3u, ipq.size());
+    ASSERT_EQ(3u, ipq.size());
 
     ipq.pop();
-    EXPECT_EQ(2u, ipq.size());
+    ASSERT_EQ(2u, ipq.size());
     EXPECT_FALSE(ipq.contains(a));
     EXPECT_TRUE(ipq.contains(b));
     EXPECT_TRUE(ipq.contains(c));
 
     ipq.pop();
-    EXPECT_EQ(1u, ipq.size());
+    ASSERT_EQ(1u, ipq.size());
     EXPECT_FALSE(ipq.contains(a));
     EXPECT_FALSE(ipq.contains(b));
     EXPECT_TRUE(ipq.contains(c));
 
     ipq.pop();
-    EXPECT_EQ(0u, ipq.size());
+    ASSERT_EQ(0u, ipq.size());
     EXPECT_FALSE(ipq.contains(a));
     EXPECT_FALSE(ipq.contains(b));
     EXPECT_FALSE(ipq.contains(c));
diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
index f27d129..a15f95b 100644
--- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -16,9 +16,12 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <stdio.h>
+
 #include <thread>
 
-#include <stdio.h>
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
@@ -29,6 +32,20 @@
 
 using std::unique_ptr;
 
+namespace {
+
+std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 10);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+} // anonymous namespace
+
 #ifdef __ANDROID__
 TEST(LogEventQueue_test, TestGoodConsumer) {
     LogEventQueue queue(50);
@@ -36,8 +53,7 @@
     std::thread writer([&queue, timeBaseNs] {
         for (int i = 0; i < 100; i++) {
             int64_t oldestEventNs;
-            bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000),
-                                      &oldestEventNs);
+            bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs);
             EXPECT_TRUE(success);
             std::this_thread::sleep_for(std::chrono::milliseconds(1));
         }
@@ -63,8 +79,7 @@
         int failure_count = 0;
         int64_t oldestEventNs;
         for (int i = 0; i < 100; i++) {
-            bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000),
-                                      &oldestEventNs);
+            bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs);
             if (!success) failure_count++;
             std::this_thread::sleep_for(std::chrono::milliseconds(1));
         }
diff --git a/cmds/statsd/tests/metadata_util_test.cpp b/cmds/statsd/tests/metadata_util_test.cpp
new file mode 100644
index 0000000..7707890
--- /dev/null
+++ b/cmds/statsd/tests/metadata_util_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 <gtest/gtest.h>
+
+#include "metadata_util.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) {
+    HashableDimensionKey dim;
+    HashableDimensionKey dim2;
+    int pos1[] = {1, 1, 1};
+    int pos2[] = {1, 1, 2};
+    int pos3[] = {1, 1, 3};
+    int pos4[] = {2, 0, 0};
+    Field field1(10, pos1, 2);
+    Field field2(10, pos2, 2);
+    Field field3(10, pos3, 2);
+    Field field4(10, pos4, 0);
+
+    Value value1((int32_t)10025);
+    Value value2("tag");
+    Value value3((int32_t)987654);
+    Value value4((int32_t)99999);
+
+    dim.addValue(FieldValue(field1, value1));
+    dim.addValue(FieldValue(field2, value2));
+    dim.addValue(FieldValue(field3, value3));
+    dim.addValue(FieldValue(field4, value4));
+
+    dim2.addValue(FieldValue(field1, value1));
+    dim2.addValue(FieldValue(field2, value2));
+
+    MetricDimensionKey dimKey(dim, dim2);
+
+    metadata::MetricDimensionKey metadataDimKey;
+    writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey);
+
+    MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey);
+
+    ASSERT_EQ(loadedDimKey, dimKey);
+    ASSERT_EQ(std::hash<MetricDimensionKey>{}(loadedDimKey),
+            std::hash<MetricDimensionKey>{}(dimKey));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 67c704e..bb8e7bf 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -13,16 +13,19 @@
 // limitations under the License.
 
 #include "src/metrics/CountMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <math.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "metrics_test_helper.h"
+#include "src/stats_log_util.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
 using std::set;
@@ -35,16 +38,44 @@
 namespace os {
 namespace statsd {
 
+
+namespace {
 const ConfigKey kConfigKey(0, 12345);
 
+void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeString(statsEvent, uid.c_str());
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+}  // namespace
+
+// Setup for parameterized tests.
+class CountMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket,
+                         CountMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
 TEST(CountMetricProducerTest, TestFirstBucket) {
     CountMetric metric;
     metric.set_id(1);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      5, 600 * NS_PER_SEC + NS_PER_SEC/2);
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
     EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, countProducer.mCurrentBucketNum);
     EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -61,45 +92,46 @@
     metric.set_id(1);
     metric.set_bucket(ONE_MINUTE);
 
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.init();
-    LogEvent event2(tagId, bucketStartTimeNs + 2);
-    event2.init();
-
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      bucketStartTimeNs, bucketStartTimeNs);
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, bucketStartTimeNs, bucketStartTimeNs);
 
     // 2 events in bucket 1.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
+
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // Flushes at event #2.
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 2);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
 
     // Flushes.
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
+    ASSERT_EQ(1UL, buckets.size());
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(2LL, buckets[0].mCount);
 
     // 1 matched event happens in bucket 2.
-    LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
-    event3.init();
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 2, tagId);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1];
     EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
     EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
@@ -107,11 +139,11 @@
 
     // nothing happens in bucket 3. we should not record anything for bucket 3.
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(2UL, buckets3.size());
+    ASSERT_EQ(2UL, buckets3.size());
 }
 
 TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
@@ -123,37 +155,38 @@
     metric.set_bucket(ONE_MINUTE);
     metric.set_condition(StringToId("SCREEN_ON"));
 
-    LogEvent event1(1, bucketStartTimeNs + 1);
-    event1.init();
-
-    LogEvent event2(1, bucketStartTimeNs + 10);
-    event2.init();
-
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs);
+    CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                      bucketStartTimeNs, bucketStartTimeNs);
 
     countProducer.onConditionChanged(true, bucketStartTimeNs);
+
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, /*atomId=*/1);
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
 
     countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+
     // Upon this match event, the matched event1 is flushed.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 10, /*atomId=*/1);
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
 
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    {
-        const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-        EXPECT_EQ(1UL, buckets.size());
-        const auto& bucketInfo = buckets[0];
-        EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
-        EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
-        EXPECT_EQ(1LL, bucketInfo.mCount);
-    }
+
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+    ASSERT_EQ(1UL, buckets.size());
+    const auto& bucketInfo = buckets[0];
+    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+    EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
@@ -172,50 +205,52 @@
     buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what());
     buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition());
 
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.write("111");  // uid
-    event1.init();
-    ConditionKey key1;
-    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] =
-        {getMockedDimensionKey(conditionTagId, 2, "111")};
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111");
 
-    LogEvent event2(tagId, bucketStartTimeNs + 10);
-    event2.write("222");  // uid
-    event2.init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 10, tagId, /*uid=*/"222");
+
+    ConditionKey key1;
+    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {
+            getMockedDimensionKey(conditionTagId, 2, "111")};
+
     ConditionKey key2;
-    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] =
-        {getMockedDimensionKey(conditionTagId, 2, "222")};
+    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {
+            getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
 
-    EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
+    EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse));
 
-    CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
-                                      bucketStartTimeNs, bucketStartTimeNs);
+    EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
+
+    CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/,
+                                      {ConditionState::kUnknown}, wizard, bucketStartTimeNs,
+                                      bucketStartTimeNs);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 1);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
+    ASSERT_EQ(1UL, buckets.size());
     const auto& bucketInfo = buckets[0];
     EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
     EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
-TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) {
     sp<AlarmMonitor> alarmMonitor;
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
 
     int tagId = 1;
     int conditionTagId = 2;
@@ -226,57 +261,66 @@
     Alert alert;
     alert.set_num_buckets(3);
     alert.set_trigger_if_sum_gt(2);
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.write("111");  // uid
-    event1.init();
+
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
                                       bucketStartTimeNs, bucketStartTimeNs);
 
     sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
     EXPECT_TRUE(anomalyTracker != nullptr);
 
-    // Bucket is flushed yet.
+    // Bucket is not flushed yet.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
-    // App upgrade forces bucket flush.
+    // App upgrade or boot complete forces bucket flush.
     // Check that there's a past bucket and the bucket end is not adjusted.
-    countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((long long)bucketStartTimeNs,
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            countProducer.notifyAppUpgrade(eventTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            countProducer.onStatsdInitCompleted(eventTimeNs);
+            break;
+    }
+    ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
-    EXPECT_EQ((long long)eventUpgradeTimeNs,
+    EXPECT_EQ(eventTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, countProducer.getCurrentBucketNum());
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
     // Anomaly tracker only contains full buckets.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs();
     // Next event occurs in same bucket as partial bucket created.
-    LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10);
-    event2.write("222");  // uid
-    event2.init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, countProducer.getCurrentBucketNum());
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     // Third event in following bucket.
-    LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10);
-    event3.write("333");  // uid
-    event3.init();
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + 62 * NS_PER_SEC + 10, tagId, /*uid=*/"333");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
-    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, countProducer.getCurrentBucketNum());
     EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
 
     int tagId = 1;
     int conditionTagId = 2;
@@ -284,41 +328,48 @@
     CountMetric metric;
     metric.set_id(1);
     metric.set_bucket(ONE_MINUTE);
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.write("111");  // uid
-    event1.init();
+
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
                                       bucketStartTimeNs, bucketStartTimeNs);
 
     // Bucket is flushed yet.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
-    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, countProducer.mPastBuckets.size());
 
-    // App upgrade forces bucket flush.
-    // Check that there's a past bucket and the bucket end is not adjusted.
-    countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)bucketStartTimeNs,
+    // App upgrade or boot complete forces bucket flush.
+    // Check that there's a past bucket and the bucket end is not adjusted since the upgrade
+    // occurred after the bucket end time.
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            countProducer.notifyAppUpgrade(eventTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            countProducer.onStatsdInitCompleted(eventTimeNs);
+            break;
+    }
+    ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
 
     // Next event occurs in same bucket as partial bucket created.
-    LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10);
-    event2.write("222");  // uid
-    event2.init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 70 * NS_PER_SEC + 10, tagId, /*uid=*/"222");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
-    EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
 
     // Third event in following bucket.
-    LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10);
-    event3.write("333");  // uid
-    event3.init();
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
-    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs,
+    ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ((int64_t)eventTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
@@ -344,38 +395,39 @@
     metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      bucketStartTimeNs, bucketStartTimeNs);
+
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, bucketStartTimeNs, bucketStartTimeNs);
 
     sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
 
     int tagId = 1;
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.init();
-    LogEvent event2(tagId, bucketStartTimeNs + 2);
-    event2.init();
-    LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
-    event3.init();
-    LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
-    event4.init();
-    LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
-    event5.init();
-    LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
-    event6.init();
-    LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC);
-    event7.init();
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + 2 * bucketSizeNs + 1, tagId);
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event4, bucketStartTimeNs + 3 * bucketSizeNs + 1, tagId);
+    LogEvent event5(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event5, bucketStartTimeNs + 3 * bucketSizeNs + 2, tagId);
+    LogEvent event6(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event6, bucketStartTimeNs + 3 * bucketSizeNs + 3, tagId);
+    LogEvent event7(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event7, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, tagId);
 
     // Two events in bucket #0.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
-    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
-    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
@@ -383,17 +435,37 @@
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6);
-    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
     // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+              std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
-    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+              std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+}
+
+TEST(CountMetricProducerTest, TestOneWeekTimeUnit) {
+    CountMetric metric;
+    metric.set_id(1);
+    metric.set_bucket(ONE_WEEK);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    int64_t oneDayNs = 24 * 60 * 60 * 1e9;
+    int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
+
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard,
+                                      oneDayNs, fiveWeeksNs);
+
+    int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
+
+    EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(4, countProducer.mCurrentBucketNum);
+    EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs());
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index b294cad..05cfa37b 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -13,17 +13,21 @@
 // limitations under the License.
 
 #include "src/metrics/DurationMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "metrics_test_helper.h"
-#include "src/condition/ConditionWizard.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+
 #include <set>
 #include <unordered_map>
 #include <vector>
 
+#include "metrics_test_helper.h"
+#include "src/condition/ConditionWizard.h"
+#include "src/stats_log_util.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
 using namespace android::os::statsd;
 using namespace testing;
 using android::sp;
@@ -37,7 +41,26 @@
 namespace os {
 namespace statsd {
 
+
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
+void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+}  // namespace
+
+// Setup for parameterized tests.
+class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket,
+                         DurationMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
 
 TEST(DurationMetricTrackerTest, TestFirstBucket) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -47,9 +70,11 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC/2);
+
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /*no condition*/, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
 
     EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, durationProducer.mCurrentBucketNum);
@@ -67,24 +92,26 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     int tagId = 1;
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.init();
-    LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2);
-    event2.init();
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId);
 
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /*no condition*/, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
-    EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
     EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 durationProducer.mPastBuckets.end());
     const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(2UL, buckets.size());
+    ASSERT_EQ(2UL, buckets.size());
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(bucketSizeNs - 1LL, buckets[0].mDuration);
@@ -104,19 +131,21 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     int tagId = 1;
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.init();
-    LogEvent event2(tagId, bucketStartTimeNs + 2);
-    event2.init();
-    LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1);
-    event3.init();
-    LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3);
-    event4.init();
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId);
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+
     DurationMetricProducer durationProducer(
-            kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
+            1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+            wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
     durationProducer.mCondition = ConditionState::kFalse;
 
     EXPECT_FALSE(durationProducer.mCondition);
@@ -125,17 +154,17 @@
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event3);
     durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
-    EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
     EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 durationProducer.mPastBuckets.end());
     const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets2.size());
+    ASSERT_EQ(1UL, buckets2.size());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs);
     EXPECT_EQ(1LL, buckets2[0].mDuration);
@@ -152,19 +181,21 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     int tagId = 1;
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    event1.init();
-    LogEvent event2(tagId, bucketStartTimeNs + 2);
-    event2.init();
-    LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1);
-    event3.init();
-    LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3);
-    event4.init();
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId);
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+
     DurationMetricProducer durationProducer(
-            kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
+            1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+            wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
     EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition);
     EXPECT_FALSE(durationProducer.isConditionSliced());
@@ -172,21 +203,21 @@
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event3);
     durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
-    EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
     const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets2.size());
+    ASSERT_EQ(1UL, buckets2.size());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs);
     EXPECT_EQ(1LL, buckets2[0].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) {
     /**
      * The duration starts from the first bucket, through the two partial buckets (10-70sec),
      * another bucket, and ends at the beginning of the next full bucket.
@@ -198,10 +229,6 @@
      */
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
 
     DurationMetric metric;
@@ -210,40 +237,53 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
-    LogEvent start_event(tagId, startTimeNs);
-    start_event.init();
-    durationProducer.onMatchedLogEvent(1 /* start index*/, start_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration);
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs);
+    EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
-    LogEvent end_event(tagId, endTimeNs);
-    end_event.init();
-    durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event);
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(3UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs);
+    ASSERT_EQ(3UL, buckets.size());
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
     EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) {
     /**
      * Expected buckets (start at 11s, upgrade at 75s, end at 135s):
      *  - [10,70]: 59 secs
@@ -252,10 +292,6 @@
      */
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
 
     DurationMetric metric;
@@ -264,47 +300,57 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
-    LogEvent start_event(tagId, startTimeNs);
-    start_event.init();
-    durationProducer.onMatchedLogEvent(1 /* start index*/, start_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    ASSERT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs);
+    EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
-    LogEvent end_event(tagId, endTimeNs);
-    end_event.init();
-    durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event);
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(3UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs);
+    ASSERT_EQ(3UL, buckets.size());
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs,
+              buckets[2].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) {
     sp<AlarmMonitor> alarmMonitor;
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
-
     int tagId = 1;
 
     // Setup metric with alert.
@@ -318,117 +364,145 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
     sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor);
     EXPECT_TRUE(anomalyTracker != nullptr);
 
-    LogEvent start_event(tagId, startTimeNs);
-    start_event.init();
-    durationProducer.onMatchedLogEvent(1 /* start index*/, start_event);
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
+
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
-    LogEvent end_event(tagId, endTimeNs);
-    end_event.init();
-    durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event);
+    int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
 
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
               anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
 
     DurationMetric metric;
     metric.set_id(1);
     metric.set_bucket(ONE_MINUTE);
     metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
-    LogEvent event1(tagId, startTimeNs);
-    event1.write("111");  // uid
-    event1.init();
+
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
-    LogEvent start_event(tagId, startTimeNs);
-    start_event.init();
-    durationProducer.onMatchedLogEvent(1 /* start index*/, start_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
-    LogEvent end_event(tagId, endTimeNs);
-    end_event.init();
-    durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
 
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
+    ASSERT_EQ(1UL, buckets.size());
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
-
     int tagId = 1;
 
     DurationMetric metric;
     metric.set_id(1);
     metric.set_bucket(ONE_MINUTE);
     metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
-    LogEvent event1(tagId, startTimeNs);
-    event1.write("111");  // uid
-    event1.init();
+
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
-    DurationMetricProducer durationProducer(
-            kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
-    LogEvent start_event(tagId, startTimeNs);
-    start_event.init();
-    durationProducer.onMatchedLogEvent(1 /* start index*/, start_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+    DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {},
+                                            1 /* start index */, 2 /* stop index */,
+                                            3 /* stop_all index */, false /*nesting*/, wizard,
+                                            dimensions, bucketStartTimeNs, bucketStartTimeNs);
+
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
 
     // Stop occurs in the same partial bucket as created for the app upgrade.
-    LogEvent end_event(tagId, endTimeNs);
-    end_event.init();
-    durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event);
-    EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
+    durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
+    ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs);
+    ASSERT_EQ(1UL, buckets.size());
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
 }
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index d2fd95c..dfbb9da 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -13,14 +13,17 @@
 // limitations under the License.
 
 #include "src/metrics/EventMetricProducer.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "metrics_test_helper.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
 using std::set;
@@ -35,6 +38,17 @@
 
 const ConfigKey kConfigKey(0, 12345);
 
+namespace {
+void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, string str) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeString(statsEvent, str.c_str());
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+}  // anonymous namespace
+
 TEST(EventMetricProducerTest, TestNoCondition) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -43,19 +57,31 @@
     EventMetric metric;
     metric.set_id(1);
 
-    LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1);
-    LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      bucketStartTimeNs);
+    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, bucketStartTimeNs);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
 
-    // TODO(b/110561136): get the report and check the content after the ProtoOutputStream change
-    // is done eventProducer.onDumpReport();
+    // Check dump report content.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/,
+                               true /*erase data*/, FAST, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_event_metrics());
+    ASSERT_EQ(2, report.event_metrics().data_size());
+    EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 2, report.event_metrics().data(1).elapsed_timestamp_nanos());
 }
 
 TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) {
@@ -67,12 +93,16 @@
     metric.set_id(1);
     metric.set_condition(StringToId("SCREEN_ON"));
 
-    LogEvent event1(1, bucketStartTimeNs + 1);
-    LogEvent event2(1, bucketStartTimeNs + 10);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
+    EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
+                                      {ConditionState::kUnknown}, wizard, bucketStartTimeNs);
 
     eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -81,8 +111,16 @@
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
 
-    // TODO: get the report and check the content after the ProtoOutputStream change is done.
-    // eventProducer.onDumpReport();
+    // Check dump report content.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/,
+                               true /*erase data*/, FAST, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_event_metrics());
+    ASSERT_EQ(1, report.event_metrics().data_size());
+    EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos());
 }
 
 TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) {
@@ -100,30 +138,40 @@
     buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what());
     buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition());
 
-    LogEvent event1(tagId, bucketStartTimeNs + 1);
-    EXPECT_TRUE(event1.write("111"));
-    event1.init();
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1, "111");
     ConditionKey key1;
-    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")};
+    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {
+            getMockedDimensionKey(conditionTagId, 2, "111")};
 
-    LogEvent event2(tagId, bucketStartTimeNs + 10);
-    EXPECT_TRUE(event2.write("222"));
-    event2.init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10, "222");
     ConditionKey key2;
-    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
+    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {
+            getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
+    // Condition is false for first event.
+    EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse));
+    // Condition is true for second event.
+    EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
 
-    EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
-
-    EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
+    EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
+                                      {ConditionState::kUnknown}, wizard, bucketStartTimeNs);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
 
-    // TODO: get the report and check the content after the ProtoOutputStream change is done.
-    // eventProducer.onDumpReport();
+    // Check dump report content.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/,
+                               true /*erase data*/, FAST, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_event_metrics());
+    ASSERT_EQ(1, report.event_metrics().data_size());
+    EXPECT_EQ(bucketStartTimeNs + 10, report.event_metrics().data(0).elapsed_timestamp_nanos());
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index b9553a8..5997bed 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -12,19 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/matchers/SimpleLogMatchingTracker.h"
 #include "src/metrics/GaugeMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "logd/LogEvent.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <math.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "logd/LogEvent.h"
+#include "metrics_test_helper.h"
+#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/stats_log_util.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
 using std::set;
@@ -38,6 +42,8 @@
 namespace os {
 namespace statsd {
 
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
@@ -48,7 +54,30 @@
 const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
 const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
-const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+
+shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1,
+                                  int32_t value2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, value1);
+    AStatsEvent_writeString(statsEvent, str1.c_str());
+    AStatsEvent_writeInt32(statsEvent, value2);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+}  // anonymous namespace
+
+// Setup for parameterized tests.
+class GaugeMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket,
+                         GaugeMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
 
 /*
  * Tests that the first bucket works correctly
@@ -75,13 +104,11 @@
 
     // statsd started long ago.
     // The metric starts in the middle of the bucket
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
-                                      pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, -1, -1,
+                                      tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
-
     EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum);
     EXPECT_EQ(660000000005, gaugeProducer.getCurrentBucketEndTimeNs());
@@ -103,60 +130,49 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(3);
-                event->write("some value");
-                event->write(11);
-                event->init();
-                data->push_back(event);
+                data->push_back(makeLogEvent(tagId, eventTimeNs + 10, 3, "some value", 11));
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
-
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
+                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(10);
-    event->write("some value");
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(makeLogEvent(tagId, bucket2StartTimeNs + 1, 10, "some value", 11));
 
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
     EXPECT_EQ(INT, it->mValue.getType());
     EXPECT_EQ(10, it->mValue.int_value);
     it++;
     EXPECT_EQ(11, it->mValue.int_value);
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms
-        .front().mFields->begin()->mValue.int_value);
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()
+                         ->second.back()
+                         .mGaugeAtoms.front()
+                         .mFields->begin()
+                         ->mValue.int_value);
 
     allData.clear();
-    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10);
-    event2->write(24);
-    event2->write("some value");
-    event2->write(25);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(makeLogEvent(tagId, bucket3StartTimeNs + 10, 24, "some value", 25));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
     EXPECT_EQ(INT, it->mValue.getType());
     EXPECT_EQ(24, it->mValue.int_value);
@@ -164,8 +180,8 @@
     EXPECT_EQ(INT, it->mValue.getType());
     EXPECT_EQ(25, it->mValue.int_value);
     // One dimension.
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
     it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin();
     EXPECT_EQ(INT, it->mValue.getType());
     EXPECT_EQ(10L, it->mValue.int_value);
@@ -174,10 +190,10 @@
     EXPECT_EQ(11L, it->mValue.int_value);
 
     gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs);
-    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
     // One dimension.
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size());
     it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin();
     EXPECT_EQ(INT, it->mValue.getType());
     EXPECT_EQ(24L, it->mValue.int_value);
@@ -186,7 +202,7 @@
     EXPECT_EQ(25L, it->mValue.int_value);
 }
 
-TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) {
     sp<AlarmMonitor> alarmMonitor;
     GaugeMetric metric;
     metric.set_id(metricId);
@@ -204,11 +220,12 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard,
                                       -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
@@ -216,58 +233,64 @@
     sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
     EXPECT_TRUE(anomalyTracker != nullptr);
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     // Partial buckets are not sent to anomaly tracker.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     // Create an event in the same partial bucket.
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
-    event2->write(1);
-    event2->write(10);
-    event2->init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+    EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     // Partial buckets are not sent to anomaly tracker.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     // Next event should trigger creation of new bucket and send previous full bucket to anomaly
     // tracker.
-    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
-    event3->write(1);
-    event3->write(10);
-    event3->init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(1L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ((int64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     // Next event should trigger creation of new bucket.
-    shared_ptr<LogEvent> event4 =
-            make_shared<LogEvent>(tagId, bucketStartTimeNs + 125 * NS_PER_SEC);
-    event4->write(1);
-    event4->write(10);
-    event4->init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 125 * NS_PER_SEC, 1, 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     EXPECT_EQ(2L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) {
     GaugeMetric metric;
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
@@ -284,59 +307,59 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             new EventMatcherWizard({new SimpleLogMatchingTracker(
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
-
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             .WillOnce(Return(false))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs);
-                event->write("some value");
-                event->write(2);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 2));
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
+                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event->write("some value");
-    event->write(1);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
                          .mFields->begin()
                          ->mValue.int_value);
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
                          .mFields->begin()
                          ->mValue.int_value);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 1);
-    event->write("some value");
-    event->write(3);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 1, 3));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
                          .mFields->begin()
@@ -358,38 +381,35 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false));
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(false));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
+                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event->write("some value");
-    event->write(1);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
                          .mFields->begin()
                          ->mValue.int_value);
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
-    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+    ASSERT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
     EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
                          .mFields->begin()
@@ -411,51 +431,48 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+
+    int64_t conditionChangeNs = bucketStartTimeNs + 8;
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, conditionChangeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write("some value");
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs + 10, 100));
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
+                                      {ConditionState::kUnknown}, wizard, logEventMatcherIndex,
+                                      eventMatcherWizard, tagId, -1, tagId, bucketStartTimeNs,
+                                      bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
-    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    gaugeProducer.onConditionChanged(true, conditionChangeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()
                            ->second.front()
                            .mFields->begin()
                            ->mValue.int_value);
-    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size());
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write("some value");
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()
                            ->second.front()
                            .mFields->begin()
                            ->mValue.int_value);
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()
                            ->second.back()
                            .mGaugeAtoms.front()
@@ -464,8 +481,8 @@
 
     gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10);
     gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10);
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
     EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()
                             ->second.back()
                             .mGaugeAtoms.front()
@@ -485,75 +502,61 @@
     dim->set_field(tagId);
     dim->add_child()->set_field(1);
 
-    dim = metric.mutable_dimensions_in_condition();
-    dim->set_field(conditionTag);
-    dim->add_child()->set_field(1);
-
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, _, _, _, _, _))
+    EXPECT_CALL(*wizard, query(_, _, _))
             .WillRepeatedly(
                     Invoke([](const int conditionIndex, const ConditionKey& conditionParameters,
-                              const vector<Matcher>& dimensionFields, const bool isSubsetDim,
-                              const bool isPartialLink,
-                              std::unordered_set<HashableDimensionKey>* dimensionKeySet) {
-                        dimensionKeySet->clear();
+                              const bool isPartialLink) {
                         int pos[] = {1, 0, 0};
                         Field f(conditionTag, pos, 0);
                         HashableDimensionKey key;
                         key.mutableValues()->emplace_back(f, Value((int32_t)1000000));
-                        dimensionKeySet->insert(key);
 
                         return ConditionState::kTrue;
                     }));
 
+    int64_t sliceConditionChangeNs = bucketStartTimeNs + 8;
+
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, sliceConditionChangeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(1000);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs + 10, 1000, 100));
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
+                                      {ConditionState::kUnknown}, wizard, logEventMatcherIndex,
+                                      eventMatcherWizard, tagId, -1, tagId, bucketStartTimeNs,
+                                      bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
-    gaugeProducer.onSlicedConditionMayChange(true, bucketStartTimeNs + 8);
+    gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs);
 
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     const auto& key = gaugeProducer.mCurrentSlicedBucket->begin()->first;
-    EXPECT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1000, key.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
 
-    EXPECT_EQ(1UL, key.getDimensionKeyInCondition().getValues().size());
-    EXPECT_EQ(1000000, key.getDimensionKeyInCondition().getValues()[0].mValue.int_value);
-
-    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size());
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(1000);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1000, 110));
     gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
 }
 
 TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) {
@@ -561,9 +564,10 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false));
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(false));
 
     GaugeMetric metric;
     metric.set_id(metricId);
@@ -576,13 +580,13 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
+                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     gaugeProducer.prepareFirstBucket();
 
     Alert alert;
@@ -595,13 +599,11 @@
     sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
 
     int tagId = 1;
-    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event1->write("some value");
-    event1->write(13);
-    event1->init();
-
-    gaugeProducer.onDataPulled({event1}, /** succeed */ true, bucketStartTimeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 13));
+    gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()
                            ->second.front()
                            .mFields->begin()
@@ -609,13 +611,12 @@
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     std::shared_ptr<LogEvent> event2 =
-            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20);
-    event2->write("some value");
-    event2->write(15);
-    event2->init();
+            CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 20, 15);
 
-    gaugeProducer.onDataPulled({event2}, /** succeed */ true, bucketStartTimeNs + bucketSizeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    allData.clear();
+    allData.push_back(event2);
+    gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()
                            ->second.front()
                            .mFields->begin()
@@ -623,14 +624,11 @@
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
               std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC) + refPeriodSec);
 
-    std::shared_ptr<LogEvent> event3 =
-            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10);
-    event3->write("some value");
-    event3->write(26);
-    event3->init();
-
-    gaugeProducer.onDataPulled({event3}, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    allData.clear();
+    allData.push_back(
+            CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10, 26));
+    gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin()
                            ->second.front()
                            .mFields->begin()
@@ -638,13 +636,11 @@
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
               std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
-    // The event4 does not have the gauge field. Thus the current bucket value is 0.
-    std::shared_ptr<LogEvent> event4 =
-            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10);
-    event4->write("some value");
-    event4->init();
-    gaugeProducer.onDataPulled({event4}, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    // This event does not have the gauge field. Thus the current bucket value is 0.
+    allData.clear();
+    allData.push_back(CreateNoValuesLogEvent(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10));
+    gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty());
 }
 
@@ -664,51 +660,49 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(4);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 4));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-                event->write(5);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 5));
                 return true;
             }))
             .WillOnce(Return(true));
 
     int triggerId = 5;
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     gaugeProducer.prepareFirstBucket();
 
-    vector<shared_ptr<LogEvent>> allData;
+    ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
 
-    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
-    LogEvent trigger(triggerId, bucketStartTimeNs + 10);
-    trigger.init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
-    trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
-    trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
+    LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+    ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
 
-    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
+    ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
     EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin()
                          ->second.back()
                          .mGaugeAtoms[0]
@@ -738,75 +732,131 @@
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
     atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3);
-                event->write(3);
-                event->write(4);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 3, 4));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(4);
-                event->write(5);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 5));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-                event->write(4);
-                event->write(6);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 6));
                 return true;
             }))
             .WillOnce(Return(true));
 
     int triggerId = 5;
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard,
-                                      tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     gaugeProducer.prepareFirstBucket();
 
-    vector<shared_ptr<LogEvent>> allData;
+    LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+    ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size());
+    ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+    ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
 
-    LogEvent trigger(triggerId, bucketStartTimeNs + 3);
-    trigger.init();
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    trigger.setElapsedTimestampNs(bucketStartTimeNs + 10);
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
-    trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
-    trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
-    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-
-    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.size());
+    ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.size());
     auto bucketIt = gaugeProducer.mPastBuckets.begin();
-    EXPECT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size());
+    ASSERT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size());
     EXPECT_EQ(3, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value);
     EXPECT_EQ(4, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value);
     bucketIt++;
-    EXPECT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size());
+    ASSERT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size());
     EXPECT_EQ(4, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value);
     EXPECT_EQ(5, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value);
     EXPECT_EQ(6, bucketIt->second.back().mGaugeAtoms[1].mFields->begin()->mValue.int_value);
 }
 
+/*
+ * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size
+ * is smaller than the "min_bucket_size_nanos" specified in the metric config.
+ */
+TEST(GaugeMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) {
+    GaugeMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(FIVE_MINUTES);
+    metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES);
+    metric.set_min_bucket_size_nanos(10000000000);  // 10 seconds
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    UidMap uidMap;
+    SimpleAtomMatcher atomMatcher;
+    atomMatcher.set_atom_id(tagId);
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _, _))
+            // Bucket start.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 10));
+                return true;
+            }));
+
+    int triggerId = 5;
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
+                                      pullerManager);
+    gaugeProducer.prepareFirstBucket();
+
+    LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    gaugeProducer.onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */, true,
+                               FAST /* dump_latency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_gauge_metrics());
+    ASSERT_EQ(0, report.gauge_metrics().data_size());
+    ASSERT_EQ(1, report.gauge_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.gauge_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000),
+              report.gauge_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.gauge_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.gauge_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index bf04752..fda3daa 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -52,7 +52,6 @@
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
-    vector<Matcher> dimensionInCondition;
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -63,9 +62,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
@@ -79,7 +77,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(20LL, buckets[eventKey][0].mDuration);
 }
 
@@ -88,7 +86,6 @@
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -99,9 +96,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
@@ -114,7 +110,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
@@ -124,7 +120,6 @@
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -135,9 +130,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // The event starts.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -155,7 +149,7 @@
     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
 
     tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets);
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration);
     EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
@@ -165,7 +159,6 @@
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -176,9 +169,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
-                               true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // 2 starts
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -195,14 +187,13 @@
                      bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
 
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
     const HashableDimensionKey conditionDimKey = key1;
 
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
@@ -223,9 +214,8 @@
     int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                               false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
-                               false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               0, bucketStartTimeNs, bucketSizeNs, true, false, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -233,20 +223,19 @@
     tracker.noteConditionChanged(key1, false, conditionStops1);
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
-    EXPECT_EQ(0U, buckets.size());
+    ASSERT_EQ(0U, buckets.size());
 
     tracker.noteConditionChanged(key1, true, conditionStarts2);
     tracker.noteConditionChanged(key1, false, conditionStops2);
     tracker.noteStop(key1, eventStopTimeNs, false);
     tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, &buckets);
-    EXPECT_EQ(1U, buckets.size());
+    ASSERT_EQ(1U, buckets.size());
     vector<DurationBucket> item = buckets.begin()->second;
-    EXPECT_EQ(1UL, item.size());
+    ASSERT_EQ(1UL, item.size());
     EXPECT_EQ((int64_t)(13LL * NS_PER_SEC), item[0].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
@@ -273,9 +262,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -283,11 +272,11 @@
 
     // Remove the anomaly alarm when the duration is no longer fully met.
     tracker.noteConditionChanged(key1, false, eventStartTimeNs + 15 * NS_PER_SEC);
-    EXPECT_EQ(0U, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(0U, anomalyTracker->mAlarms.size());
 
     // Since the condition was off for 10 seconds, the anomaly should trigger 10 sec later.
     tracker.noteConditionChanged(key1, true, eventStartTimeNs + 25 * NS_PER_SEC);
-    EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(63ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
 }
@@ -295,7 +284,6 @@
 // This tests that we correctly compute the predicted time of an anomaly assuming that the current
 // state continues forward as-is.
 TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) {
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
@@ -333,16 +321,16 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
     tracker.noteConditionChanged(key1, false, conditionStops1);
     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);  // Condition is on already.
     tracker.noteConditionChanged(key1, true, conditionStarts2);
-    EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     auto alarm = anomalyTracker->mAlarms.begin()->second;
     int64_t anomalyFireTimeSec = alarm->timestampSec;
     EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC,
@@ -353,7 +341,7 @@
     // gets correctly taken into account in future predictAnomalyTimestampNs calculations.
     std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
     anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms);
-    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
     int64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec;
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec);
 
@@ -364,7 +352,7 @@
     tracker.noteStop(key2, eventStopTimeNs, false);
     tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1);
     // Anomaly is ongoing, but we're still in the refractory period.
-    EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ(refractoryPeriodEndsSec, (long long)(alarm->timestampSec));
 
@@ -381,7 +369,6 @@
 // Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the
 // elapsed duration of B.
 TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) {
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
@@ -416,14 +403,14 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
     tracker.noteStop(key1, eventStopTimeNs1, false);
-    EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     auto alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ(eventStopTimeNs1 + 35 * NS_PER_SEC,
               (unsigned long long)(alarm->timestampSec * NS_PER_SEC));
@@ -434,4 +421,4 @@
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
\ No newline at end of file
+#endif
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 7c2b423..1d6f7de 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -51,7 +51,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -62,9 +61,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -75,7 +74,7 @@
     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
 
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration);
 }
 
@@ -84,7 +83,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -94,9 +92,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -106,7 +103,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
 }
 
@@ -117,7 +114,6 @@
         {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -127,9 +123,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -138,7 +133,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
 }
 
@@ -147,7 +142,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -158,9 +152,8 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -168,7 +161,7 @@
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey());
     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
 
-    EXPECT_EQ(2u, buckets[eventKey].size());
+    ASSERT_EQ(2u, buckets[eventKey].size());
     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
 
@@ -176,7 +169,7 @@
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(2u, buckets[eventKey].size());
+    ASSERT_EQ(2u, buckets[eventKey].size());
     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
 }
@@ -186,13 +179,12 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _))  // #4
+    EXPECT_CALL(*wizard, query(_, key1, _))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -203,9 +195,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -215,7 +207,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(5LL, buckets[eventKey][0].mDuration);
 }
 
@@ -224,13 +216,12 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _))
+    EXPECT_CALL(*wizard, query(_, key1, _))
             .Times(2)
             .WillOnce(Return(ConditionState::kFalse))
             .WillOnce(Return(ConditionState::kTrue));
@@ -243,9 +234,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -257,7 +248,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration);
 }
 
@@ -266,13 +257,12 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _))  // #4
+    EXPECT_CALL(*wizard, query(_, key1, _))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -282,9 +272,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -297,7 +286,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(15LL, buckets[eventKey][0].mDuration);
 }
 
@@ -306,7 +295,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -324,9 +312,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                                 {anomalyTracker});
 
     // Nothing in the past bucket.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -334,7 +322,7 @@
               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
 
     tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
-    EXPECT_EQ(0u, buckets[eventKey].size());
+    ASSERT_EQ(0u, buckets[eventKey].size());
 
     int64_t event1StartTimeNs = eventStartTimeNs + 10;
     tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey());
@@ -347,7 +335,7 @@
     tracker.noteStop(kEventKey1, event1StopTimeNs, false);
 
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
-    EXPECT_EQ(1u, buckets[eventKey].size());
+    ASSERT_EQ(1u, buckets[eventKey].size());
     EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
               buckets[eventKey][0].mDuration);
 
@@ -371,7 +359,6 @@
 }
 
 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) {
-    vector<Matcher> dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -387,7 +374,7 @@
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1,
-                                 dimensionInCondition,
+
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
                                  bucketSizeNs, true, false, {anomalyTracker});
 
@@ -415,7 +402,7 @@
     for (int j = 0; j < 3; j++) {
         int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC;
         for (int i = 0; i <= 7; ++i) {
-            vector<Matcher> dimensionInCondition;
+
             Alert alert;
             alert.set_id(101);
             alert.set_metric_id(1);
@@ -431,9 +418,8 @@
             sp<AlarmMonitor> alarmMonitor;
             sp<DurationAnomalyTracker> anomalyTracker =
                 new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY,
-                                         wizard, 1, dimensionInCondition,
-                                         true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
+                                         1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
                                          bucketSizeNs, true, false, {anomalyTracker});
 
             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
@@ -472,7 +458,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -491,20 +476,20 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_TRUE(tracker.mStarted.empty());
-    EXPECT_EQ(10LL, tracker.mDuration); // 10ns
+    EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration);  // 10ns
 
-    EXPECT_EQ(0u, tracker.mStarted.size());
+    ASSERT_EQ(0u, tracker.mStarted.size());
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey());
-    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     EXPECT_EQ((long long)(52ULL * NS_PER_SEC),  // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up
               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
     // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However,
@@ -522,7 +507,6 @@
 
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
-    vector<Matcher> dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -541,34 +525,34 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
-                                 true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
+                                 false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
-    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey);  // start key1
+    ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
     tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later)
-    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
-    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey);  // start key1 again
+    ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
-    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey);  // start key2
+    ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
     tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1
-    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
@@ -576,11 +560,11 @@
     // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
     std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
     anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
-    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
 
     tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2
-    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
 }
 
@@ -589,4 +573,4 @@
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
\ No newline at end of file
+#endif
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 2262c76..5666501 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -12,21 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/matchers/SimpleLogMatchingTracker.h"
 #include "src/metrics/ValueMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <math.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "metrics_test_helper.h"
+#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
-using android::util::ProtoReader;
 using std::make_shared;
 using std::set;
 using std::shared_ptr;
@@ -39,6 +41,8 @@
 namespace os {
 namespace statsd {
 
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
@@ -56,10 +60,18 @@
 static void assertPastBucketValuesSingleKey(
         const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets,
         const std::initializer_list<int>& expectedValuesList,
-        const std::initializer_list<int64_t>& expectedDurationNsList) {
-    std::vector<int> expectedValues(expectedValuesList);
-    std::vector<int64_t> expectedDurationNs(expectedDurationNsList);
+        const std::initializer_list<int64_t>& expectedDurationNsList,
+        const std::initializer_list<int64_t>& expectedStartTimeNsList,
+        const std::initializer_list<int64_t>& expectedEndTimeNsList) {
+    vector<int> expectedValues(expectedValuesList);
+    vector<int64_t> expectedDurationNs(expectedDurationNsList);
+    vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList);
+    vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList);
+
     ASSERT_EQ(expectedValues.size(), expectedDurationNs.size());
+    ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size());
+    ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size());
+
     if (expectedValues.size() == 0) {
         ASSERT_EQ(0, mPastBuckets.size());
         return;
@@ -68,27 +80,23 @@
     ASSERT_EQ(1, mPastBuckets.size());
     ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size());
 
-    auto buckets = mPastBuckets.begin()->second;
+    const vector<ValueBucket>& buckets = mPastBuckets.begin()->second;
     for (int i = 0; i < expectedValues.size(); i++) {
         EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value)
                 << "Values differ at index " << i;
         EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs)
                 << "Condition duration value differ at index " << i;
+        EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs)
+                << "Start time differs at index " << i;
+        EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs)
+                << "End time differs at index " << i;
     }
 }
 
+}  // anonymous namespace
+
 class ValueMetricProducerTestHelper {
-
- public:
-    static shared_ptr<LogEvent> createEvent(int64_t eventTimeNs, int64_t value) {
-        shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventTimeNs);
-        event->write(tagId);
-        event->write(value);
-        event->write(value);
-        event->init();
-        return event;
-    }
-
+public:
     static sp<ValueMetricProducer> createValueProducerNoConditions(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) {
         UidMap uidMap;
@@ -98,19 +106,22 @@
                 new EventMatcherWizard({new SimpleLogMatchingTracker(
                         atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
+                .WillOnce(Return());
+        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _))
+                .WillRepeatedly(Return());
 
-        sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
-                kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                logEventMatcherIndex, eventMatcherWizard, tagId,
-                bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+        sp<ValueMetricProducer> valueProducer =
+                new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                        wizard, logEventMatcherIndex, eventMatcherWizard, tagId,
+                                        bucketStartTimeNs, bucketStartTimeNs, pullerManager);
         valueProducer->prepareFirstBucket();
         return valueProducer;
     }
 
     static sp<ValueMetricProducer> createValueProducerWithCondition(
-            sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) {
+            sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
+            ConditionState conditionAfterFirstBucketPrepared) {
         UidMap uidMap;
         SimpleAtomMatcher atomMatcher;
         atomMatcher.set_atom_id(tagId);
@@ -118,15 +129,67 @@
                 new EventMatcherWizard({new SimpleLogMatchingTracker(
                         atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
+                .WillOnce(Return());
+        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _))
+                .WillRepeatedly(Return());
 
-        sp<ValueMetricProducer> valueProducer =
-                new ValueMetricProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
-                                        eventMatcherWizard, tagId, bucketStartTimeNs,
-                                        bucketStartTimeNs, pullerManager);
+        sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
+                kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard,
+                logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs,
+                bucketStartTimeNs, pullerManager);
         valueProducer->prepareFirstBucket();
-        valueProducer->mCondition = ConditionState::kFalse;
+        valueProducer->mCondition = conditionAfterFirstBucketPrepared;
+        return valueProducer;
+    }
+
+    static sp<ValueMetricProducer> createValueProducerWithState(
+            sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
+            vector<int32_t> slicedStateAtoms,
+            unordered_map<int, unordered_map<int, int64_t>> stateGroupMap) {
+        UidMap uidMap;
+        SimpleAtomMatcher atomMatcher;
+        atomMatcher.set_atom_id(tagId);
+        sp<EventMatcherWizard> eventMatcherWizard =
+                new EventMatcherWizard({new SimpleLogMatchingTracker(
+                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+        sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
+                .WillOnce(Return());
+        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _))
+                .WillRepeatedly(Return());
+
+        sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
+                kConfigKey, metric, -1 /* no condition */, {}, wizard, logEventMatcherIndex,
+                eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager, {},
+                {}, slicedStateAtoms, stateGroupMap);
+        valueProducer->prepareFirstBucket();
+        return valueProducer;
+    }
+
+    static sp<ValueMetricProducer> createValueProducerWithConditionAndState(
+            sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
+            vector<int32_t> slicedStateAtoms,
+            unordered_map<int, unordered_map<int, int64_t>> stateGroupMap,
+            ConditionState conditionAfterFirstBucketPrepared) {
+        UidMap uidMap;
+        SimpleAtomMatcher atomMatcher;
+        atomMatcher.set_atom_id(tagId);
+        sp<EventMatcherWizard> eventMatcherWizard =
+                new EventMatcherWizard({new SimpleLogMatchingTracker(
+                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+        sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+        EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
+                .WillOnce(Return());
+        EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _))
+                .WillRepeatedly(Return());
+
+        sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
+                kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown},
+                wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs,
+                bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap);
+        valueProducer->prepareFirstBucket();
+        valueProducer->mCondition = conditionAfterFirstBucketPrepared;
         return valueProducer;
     }
 
@@ -145,8 +208,27 @@
         metric.set_condition(StringToId("SCREEN_ON"));
         return metric;
     }
+
+    static ValueMetric createMetricWithState(string state) {
+        ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+        metric.add_slice_by_state(StringToId(state));
+        return metric;
+    }
+
+    static ValueMetric createMetricWithConditionAndState(string state) {
+        ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+        metric.set_condition(StringToId("SCREEN_ON"));
+        metric.add_slice_by_state(StringToId(state));
+        return metric;
+    }
 };
 
+// Setup for parameterized tests.
+class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket,
+                         ValueMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
 
 /*
  * Tests that the first bucket works correctly
@@ -166,9 +248,9 @@
 
     // statsd started long ago.
     // The metric starts in the middle of the bucket
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, -1, startTimeBase,
-                                      22, pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, -1,
+                                      startTimeBase, 22, pullerManager);
     valueProducer.prepareFirstBucket();
 
     EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10));
@@ -196,8 +278,8 @@
 
     // statsd started long ago.
     // The metric starts in the middle of the bucket
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, -1, 5,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard, -1, 5,
                                       600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager);
     valueProducer.prepareFirstBucket();
 
@@ -210,16 +292,13 @@
  * Tests pulled atoms with no conditions
  */
 TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
                 return true;
             }));
 
@@ -228,63 +307,55 @@
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(8, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(tagId);
-    event->write(23);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(23, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(23, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(12, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
     EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event->write(tagId);
-    event->write(36);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(36, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(13, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    EXPECT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
     EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value);
@@ -293,28 +364,27 @@
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs);
 }
 
-TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // Initialize bucket.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-                event->write(tagId);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1));
                 return true;
             }))
             // Partial bucket.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](
+                                     int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                     vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-                event->write(tagId);
-                event->write(5);
-                event->init();
-                data->push_back(event);
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5));
                 return true;
             }));
 
@@ -324,34 +394,32 @@
     // First bucket ends.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-    event->write(tagId);
-    event->write(2);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 2));
     valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs);
 
     // Partial buckets created in 2nd bucket.
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer->getCurrentBucketNum());
 
-    // One full bucket and one partial bucket.
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second;
-    EXPECT_EQ(2UL, buckets.size());
-    // Full bucket (2 - 1)
-    EXPECT_EQ(1, buckets[0].values[0].long_value);
-    EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs);
-    // Full bucket (5 - 3)
-    EXPECT_EQ(3, buckets[1].values[0].long_value);
-    // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2]
-    EXPECT_EQ(2, buckets[1].mConditionTrueNs);
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3},
+                                    {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs},
+                                    {bucketStartTimeNs, bucket2StartTimeNs},
+                                    {bucket2StartTimeNs, partialBucketSplitTimeNs});
 }
 
 /*
  * Tests pulled atoms with filtering
  */
 TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
@@ -364,81 +432,67 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(3);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 3, 3));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
-            kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-            logEventMatcherIndex, eventMatcherWizard, tagId,
-            bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+            kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, logEventMatcherIndex,
+            eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     valueProducer->prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(3);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 3, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(8, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(4);
-    event->write(23);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket3StartTimeNs + 1, 4, 23));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // No new data seen, so data has been cleared.
-    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(8, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event->write(3);
-    event->write(36);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
     // the base was reset
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(36, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size());
     EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs);
 }
@@ -451,61 +505,54 @@
     metric.set_use_absolute_value_on_reset(true);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true));
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(true));
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(tagId);
-    event->write(10);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(10, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(10, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
     EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs);
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event->write(tagId);
-    event->write(36);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(36, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(26, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size());
     EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
     EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value);
@@ -518,57 +565,50 @@
 TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false));
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(false));
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(tagId);
-    event->write(10);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(10, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event->write(tagId);
-    event->write(36);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(36, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(26, curInterval.value.long_value);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
     EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs);
 }
@@ -581,82 +621,81 @@
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);  // First condition change.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1);  // Second condition change.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-                event->write(tagId);
-                event->write(130);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 130));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 1);  // Third condition change.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-                event->write(tagId);
-                event->write(180);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 180));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     // startUpdated:false sum:0 start:100
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(1);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(110, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(110, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
-    EXPECT_EQ(false, curInterval.hasBase);
+    EXPECT_EQ(false, curBaseInfo.hasBase);
 
     valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1},
+                                    {bucketStartTimeNs, bucket2StartTimeNs},
+                                    {bucket2StartTimeNs, bucket3StartTimeNs});
 }
 
-TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
@@ -667,42 +706,58 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
 
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
-    event2->write(1);
-    event2->write(10);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+    // Event arrives after the bucket split.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
+
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
 
     // Next value should create a new bucket.
-    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
-    event3->write(1);
-    event3->write(10);
-    event3->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs,
+                                     bucket2StartTimeNs - partialBucketSplitTimeNs},
+                                    {bucketStartTimeNs, partialBucketSplitTimeNs},
+                                    {partialBucketSplitTimeNs, bucket2StartTimeNs});
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
 }
 
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
@@ -712,53 +767,53 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150;
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             .WillOnce(Return(true))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](
+                                     int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                     vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 149);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120));
                 return true;
             }));
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(100);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100));
 
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150});
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs},
+                                    {partialBucketSplitTimeNs});
 
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(tagId);
-    event->write(150);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150));
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
-    EXPECT_EQ(20L,
-              valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30},
-                                    {150, bucketSizeNs - 150});
+    EXPECT_EQ(2, valueProducer.getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150},
+                                    {bucket2StartTimeNs, partialBucketSplitTimeNs},
+                                    {partialBucketSplitTimeNs, bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) {
@@ -773,69 +828,72 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true));
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(true));
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(100);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100));
 
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
-    EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150);
+    ASSERT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
 }
 
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1);  // Condition change to true time.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs,
+                          bucket2StartTimeNs - 100);  // Condition change to false time.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs - 100);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 120));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 1);
 
-    valueProducer->onConditionChanged(false, bucket2StartTimeNs-100);
+    valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100);
     EXPECT_FALSE(valueProducer->mCondition);
 
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs-50, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     // Expect one full buckets already done and starting a partial bucket.
-    EXPECT_EQ(bucket2StartTimeNs-50, valueProducer->mCurrentBucketStartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs,
-              valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20},
-                                    {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)});
+                                    {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
     EXPECT_FALSE(valueProducer->mCondition);
 }
 
@@ -851,35 +909,36 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(20);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(30, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
@@ -894,59 +953,54 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
-                                      eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                      logEventMatcherIndex, eventMatcherWizard, -1,
+                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
     valueProducer.mCondition = ConditionState::kFalse;
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has 1 slice
-    EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
 
     valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15);
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(20);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer.mCurrentSlicedBucket.begin()->second[0];
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
-    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 30);
-    event3->write(1);
-    event3->write(30);
-    event3->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
-    shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 40);
-    event4->write(1);
-    event4->write(40);
-    event4->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs},
+                                    {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestAnomalyDetection) {
@@ -969,136 +1023,137 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      logEventMatcherIndex, eventMatcherWizard, -1 /*not pulled*/,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, logEventMatcherIndex, eventMatcherWizard,
+                                      -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs,
+                                      pullerManager);
     valueProducer.prepareFirstBucket();
 
     sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor);
 
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10);
 
-    shared_ptr<LogEvent> event1
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1 * NS_PER_SEC);
-    event1->write(161);
-    event1->write(10); // value of interest
-    event1->init();
-    shared_ptr<LogEvent> event2
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 + NS_PER_SEC);
-    event2->write(162);
-    event2->write(20); // value of interest
-    event2->init();
-    shared_ptr<LogEvent> event3
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC);
-    event3->write(163);
-    event3->write(130); // value of interest
-    event3->init();
-    shared_ptr<LogEvent> event4
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC);
-    event4->write(35);
-    event4->write(1); // value of interest
-    event4->init();
-    shared_ptr<LogEvent> event5
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC);
-    event5->write(45);
-    event5->write(150); // value of interest
-    event5->init();
-    shared_ptr<LogEvent> event6
-            = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC);
-    event6->write(25);
-    event6->write(160); // value of interest
-    event6->init();
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 2 + NS_PER_SEC, 20);
+
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event3, tagId,
+                                bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, 130);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event4, tagId,
+                                bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC, 1);
+
+    LogEvent event5(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event5, tagId,
+                                bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, 150);
+
+    LogEvent event6(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event6, tagId,
+                                bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC, 160);
 
     // Two events in bucket #0.
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     // Value sum == 30 <= 130.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     // Value sum == 130 <= 130.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // Three events in bucket #3.
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     // Anomaly at event 4 since Value sum == 131 > 130!
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5);
+              std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event5);
     // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+              std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event6);
     // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            std::ceil(1.0 * event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
+              std::ceil(1.0 * event6.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 }
 
 // Test value metric no condition, the pull on bucket boundary come in time and too late
 TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true));
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Return(true));
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
     // pull 1
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
 
     // startUpdated:true sum:0 start:11
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(11, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(11, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     // pull 2 at correct time
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event->write(tagId);
-    event->write(23);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     // tartUpdated:false sum:12
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(23, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(23, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
 
     // pull 3 come late.
     // The previous bucket gets closed with error. (Has start value 23, no ending)
     // Another bucket gets closed with error. (No start, but ending with 36)
     // The new bucket is back to normal.
     allData.clear();
-    event = make_shared<LogEvent>(tagId, bucket6StartTimeNs + 1);
-    event->write(tagId);
-    event->write(36);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     // startUpdated:false sum:12
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(36, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
+    // The 1st bucket is dropped because of no data
+    // The 3rd bucket is dropped due to multiple buckets being skipped.
+    ASSERT_EQ(2, valueProducer->mSkippedBuckets.size());
+
+    EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+    EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size());
+    EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+    EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs);
+
+    EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs);
+    EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs);
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size());
+    EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason);
+    EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs);
 }
 
 /*
@@ -1109,56 +1164,58 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // condition becomes true
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);  // First condition change.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }))
             // condition becomes false
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1);  // Second condition change.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     // pull on bucket boundary come late, condition change happens before it
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
-    EXPECT_EQ(false, curInterval.hasBase);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
+    EXPECT_EQ(false, curBaseInfo.hasBase);
 
     // Now the alarm is delivered.
     // since the condition turned to off before this pull finish, it has no effect
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
 
@@ -1170,87 +1227,90 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // condition becomes true
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }))
             // condition becomes false
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120));
                 return true;
             }))
             // condition becomes true again
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 25);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 25);
-                event->write(tagId);
-                event->write(130);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 25, 130));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     // startUpdated:false sum:0 start:100
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     // pull on bucket boundary come late, condition change happens before it
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 
     // condition changed to true again, before the pull alarm is delivered
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(130, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(130, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
 
     // Now the alarm is delivered, but it is considered late, the data will be used
     // for the new bucket since it was just pulled.
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 50, 140));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50);
 
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(140, curInterval.base.long_value);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(10, curInterval.value.long_value);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     allData.clear();
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 160));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30},
-                                    {bucketSizeNs - 8, bucketSizeNs - 24});
+    assertPastBucketValuesSingleKey(
+            valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24},
+            {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateMin) {
@@ -1266,36 +1326,35 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(20);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
@@ -1311,38 +1370,34 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(20);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
-    valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
-    /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */
-    /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */
-    /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */
+    valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateAvg) {
@@ -1358,39 +1413,36 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(15);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval;
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(1, curInterval.sampleSize);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(25, curInterval.value.long_value);
     EXPECT_EQ(2, curInterval.sampleSize);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
+    ASSERT_EQ(1UL, valueProducer.mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
 
     EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value -
                          12.5) < epsilon);
@@ -1409,36 +1461,34 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
-    event2->write(1);
-    event2->write(15);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(25, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
@@ -1455,63 +1505,61 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15);
-    event2->write(1);
-    event2->write(15);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(10, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(5, curInterval.value.long_value);
 
     // no change in data.
-    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-    event3->write(1);
-    event3->write(15);
-    event3->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(15, curInterval.base.long_value);
-    EXPECT_EQ(true, curInterval.hasValue);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
-    shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15);
-    event4->write(1);
-    event4->write(15);
-    event4->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(15, curInterval.base.long_value);
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(0, curInterval.value.long_value);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(15, curBaseInfo.base.long_value);
+    EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(0, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) {
@@ -1529,85 +1577,84 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
                                       pullerManager);
     valueProducer.prepareFirstBucket();
 
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    event1->write(1);
-    event1->write(10);
-    event1->write(20);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15);
-    event2->write(1);
-    event2->write(15);
-    event2->write(22);
-    event2->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
-    // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    ValueMetricProducer::Interval curInterval0 =
-        valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::Interval curInterval1 =
-        valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    EXPECT_EQ(true, curInterval0.hasBase);
-    EXPECT_EQ(10, curInterval0.base.long_value);
-    EXPECT_EQ(false, curInterval0.hasValue);
-    EXPECT_EQ(true, curInterval1.hasBase);
-    EXPECT_EQ(20, curInterval1.base.long_value);
-    EXPECT_EQ(false, curInterval1.hasValue);
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10, 20);
 
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15, 22);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+    // has one slice
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval =
+            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(10, curBaseInfo.base.long_value);
+    EXPECT_EQ(false, curInterval.hasValue);
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(20, curBaseInfo.base.long_value);
+    EXPECT_EQ(false, curInterval.hasValue);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    EXPECT_EQ(true, curInterval0.hasValue);
-    EXPECT_EQ(5, curInterval0.value.long_value);
-    EXPECT_EQ(true, curInterval1.hasValue);
-    EXPECT_EQ(2, curInterval1.value.long_value);
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(5, curInterval.value.long_value);
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(2, curInterval.value.long_value);
 
     // no change in first value field
-    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-    event3->write(1);
-    event3->write(15);
-    event3->write(25);
-    event3->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    EXPECT_EQ(true, curInterval0.hasBase);
-    EXPECT_EQ(15, curInterval0.base.long_value);
-    EXPECT_EQ(true, curInterval0.hasValue);
-    EXPECT_EQ(true, curInterval1.hasBase);
-    EXPECT_EQ(25, curInterval1.base.long_value);
-    EXPECT_EQ(true, curInterval1.hasValue);
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15, 25);
 
-    shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15);
-    event4->write(1);
-    event4->write(15);
-    event4->write(29);
-    event4->init();
-    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
-    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    EXPECT_EQ(true, curInterval0.hasBase);
-    EXPECT_EQ(15, curInterval0.base.long_value);
-    EXPECT_EQ(true, curInterval0.hasValue);
-    EXPECT_EQ(true, curInterval1.hasBase);
-    EXPECT_EQ(29, curInterval1.base.long_value);
-    EXPECT_EQ(true, curInterval1.hasValue);
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(15, curBaseInfo.base.long_value);
+    EXPECT_EQ(true, curInterval.hasValue);
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(25, curBaseInfo.base.long_value);
+    EXPECT_EQ(true, curInterval.hasValue);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15, 29);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
+    ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(15, curBaseInfo.base.long_value);
+    EXPECT_EQ(true, curInterval.hasValue);
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(29, curBaseInfo.base.long_value);
+    EXPECT_EQ(true, curInterval.hasValue);
 
     valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
 
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size());
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size());
+    ASSERT_EQ(1UL, valueProducer.mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
+    ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size());
+    ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size());
 
     EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[0].mConditionTrueNs);
     EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value);
@@ -1630,47 +1677,38 @@
     metric.set_use_zero_default_base(true);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(1);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
     auto& interval1 = iter->second[0];
+    auto iterBase = valueProducer->mCurrentBaseInfo.begin();
+    auto& baseInfo1 = iterBase->second[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(3, interval1.base.long_value);
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(3, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
     vector<shared_ptr<LogEvent>> allData;
 
     allData.clear();
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event1->write(2);
-    event1->write(4);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(11);
-    event2->init();
-    allData.push_back(event1);
-    allData.push_back(event2);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(11, interval1.base.long_value);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(11, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(8, interval1.value.long_value);
 
@@ -1680,15 +1718,23 @@
             break;
         }
     }
+    auto itBase = valueProducer->mCurrentBaseInfo.begin();
+    for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) {
+        if (itBase != iterBase) {
+            break;
+        }
+    }
     EXPECT_TRUE(it != iter);
+    EXPECT_TRUE(itBase != iterBase);
     auto& interval2 = it->second[0];
+    auto& baseInfo2 = itBase->second[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(4, interval2.base.long_value);
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(4, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_EQ(4, interval2.value.long_value);
 
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
     auto iterator = valueProducer->mPastBuckets.begin();
     EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
     EXPECT_EQ(8, iterator->second[0].values[0].long_value);
@@ -1707,107 +1753,100 @@
     metric.set_use_zero_default_base(true);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(1);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    auto iter = valueProducer->mCurrentSlicedBucket.begin();
-    auto& interval1 = iter->second[0];
-    EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(3, interval1.base.long_value);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    const auto& it = valueProducer->mCurrentSlicedBucket.begin();
+    ValueMetricProducer::Interval& interval1 = it->second[0];
+    ValueMetricProducer::BaseInfo& baseInfo1 =
+            valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0];
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(3, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
     vector<shared_ptr<LogEvent>> allData;
 
     allData.clear();
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event1->write(2);
-    event1->write(4);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(11);
-    event2->init();
-    allData.push_back(event1);
-    allData.push_back(event2);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11));
 
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(11, interval1.base.long_value);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(11, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(8, interval1.value.long_value);
 
-    auto it = valueProducer->mCurrentSlicedBucket.begin();
-    for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) {
-        if (it != iter) {
+    auto it2 = valueProducer->mCurrentSlicedBucket.begin();
+    for (; it2 != valueProducer->mCurrentSlicedBucket.end(); it2++) {
+        if (it2 != it) {
             break;
         }
     }
-    EXPECT_TRUE(it != iter);
-    auto& interval2 = it->second[0];
-    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(4, interval2.base.long_value);
+    EXPECT_TRUE(it2 != it);
+    ValueMetricProducer::Interval& interval2 = it2->second[0];
+    ValueMetricProducer::BaseInfo& baseInfo2 =
+            valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0];
+    EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(4, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_EQ(4, interval2.value.long_value);
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
 
     // next pull somehow did not happen, skip to end of bucket 3
     allData.clear();
-    event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event1->write(2);
-    event1->write(5);
-    event1->init();
-    allData.push_back(event1);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
 
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(5, interval2.base.long_value);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(5, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
 
     allData.clear();
-    event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1);
-    event1->write(2);
-    event1->write(13);
-    event1->init();
-    allData.push_back(event1);
-    event2 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1);
-    event2->write(1);
-    event2->write(5);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 13));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 1, 5));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs);
 
-    EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
-    auto it1 = std::next(valueProducer->mCurrentSlicedBucket.begin())->second[0];
-    EXPECT_EQ(true, it1.hasBase);
-    EXPECT_EQ(13, it1.base.long_value);
-    EXPECT_EQ(false, it1.hasValue);
-    EXPECT_EQ(8, it1.value.long_value);
-    auto it2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, it2.hasBase);
-    EXPECT_EQ(5, it2.base.long_value);
-    EXPECT_EQ(false, it2.hasValue);
-    EXPECT_EQ(5, it2.value.long_value);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Get new references now that entries have been deleted from the map
+    const auto& it3 = valueProducer->mCurrentSlicedBucket.begin();
+    const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin());
+    ASSERT_EQ(it3->second.size(), 1);
+    ASSERT_EQ(it4->second.size(), 1);
+    ValueMetricProducer::Interval& interval3 = it3->second[0];
+    ValueMetricProducer::Interval& interval4 = it4->second[0];
+    ValueMetricProducer::BaseInfo& baseInfo3 =
+            valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0];
+    ValueMetricProducer::BaseInfo& baseInfo4 =
+            valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0];
+
+    EXPECT_EQ(true, baseInfo3.hasBase);
+    EXPECT_EQ(5, baseInfo3.base.long_value);
+    EXPECT_EQ(false, interval3.hasValue);
+    EXPECT_EQ(5, interval3.value.long_value);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
-    EXPECT_EQ(2UL, valueProducer->mPastBuckets.size());
+
+    EXPECT_EQ(true, baseInfo4.hasBase);
+    EXPECT_EQ(13, baseInfo4.base.long_value);
+    EXPECT_EQ(false, interval4.hasValue);
+    EXPECT_EQ(8, interval4.value.long_value);
+
+    ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
 }
 
 /*
@@ -1819,50 +1858,42 @@
     metric.mutable_dimensions_in_what()->add_child()->set_field(1);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(1);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
     auto& interval1 = iter->second[0];
+    auto iterBase = valueProducer->mCurrentBaseInfo.begin();
+    auto& baseInfo1 = iterBase->second[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(3, interval1.base.long_value);
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(3, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
+
     vector<shared_ptr<LogEvent>> allData;
-
     allData.clear();
-    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event1->write(2);
-    event1->write(4);
-    event1->init();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(11);
-    event2->init();
-    allData.push_back(event1);
-    allData.push_back(event2);
-
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(true, interval1.hasBase);
-    EXPECT_EQ(11, interval1.base.long_value);
+
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, baseInfo1.hasBase);
+    EXPECT_EQ(11, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(8, interval1.value.long_value);
     EXPECT_FALSE(interval1.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) {
@@ -1870,51 +1901,57 @@
             break;
         }
     }
+    auto itBase = valueProducer->mCurrentBaseInfo.begin();
+    for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) {
+        if (itBase != iterBase) {
+            break;
+        }
+    }
     EXPECT_TRUE(it != iter);
-    auto& interval2 = it->second[0];
+    EXPECT_TRUE(itBase != iterBase);
+    auto interval2 = it->second[0];
+    auto baseInfo2 = itBase->second[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(4, interval2.base.long_value);
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(4, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_FALSE(interval2.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
 
     // next pull somehow did not happen, skip to end of bucket 3
     allData.clear();
-    event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1);
-    event1->write(2);
-    event1->write(5);
-    event1->init();
-    allData.push_back(event1);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
-	// Only one interval left. One was trimmed.
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Only one interval left. One was trimmed.
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(5, interval2.base.long_value);
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(5, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_FALSE(interval2.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     allData.clear();
-    event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1);
-    event1->write(2);
-    event1->write(14);
-    event1->init();
-    allData.push_back(event1);
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs);
 
     interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, interval2.hasBase);
-    EXPECT_EQ(14, interval2.base.long_value);
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, baseInfo2.hasBase);
+    EXPECT_EQ(14, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_FALSE(interval2.seenNewData);
     ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
     auto iterator = valueProducer->mPastBuckets.begin();
+    EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs);
+    EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs);
     EXPECT_EQ(9, iterator->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
     iterator++;
+    EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs);
+    EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs);
     EXPECT_EQ(8, iterator->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
 }
@@ -1924,33 +1961,32 @@
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     // Used by onConditionChanged.
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
 
     vector<shared_ptr<LogEvent>> allData;
     valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(false, curInterval.hasBase);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
 }
@@ -1959,38 +1995,38 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);  // Condition change to true.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }))
             .WillOnce(Return(false));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 20);
 
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     EXPECT_EQ(false, curInterval.hasValue);
-    EXPECT_EQ(false, curInterval.hasBase);
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
 }
 
@@ -1998,41 +2034,39 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(50);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 50));
                 return false;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1);  // Condition change to false.
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     // Don't directly set mCondition; the real code never does that. Go through regular code path
     // to avoid unexpected behaviors.
     // valueProducer->mCondition = ConditionState::kTrue;
     valueProducer->onConditionChanged(true, bucketStartTimeNs);
 
-    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 1);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
 }
@@ -2043,25 +2077,21 @@
     metric.set_max_pull_delay_sec(0);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 120));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     // Max delay is set to 0 so pull will exceed max delay.
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 1);
-    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
 }
 
 TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) {
@@ -2075,84 +2105,77 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return());
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
-                                      eventMatcherWizard, tagId, bucket2StartTimeNs,
-                                      bucket2StartTimeNs, pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                      logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      bucket2StartTimeNs, bucket2StartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
     valueProducer.mCondition = ConditionState::kFalse;
 
     // Event should be skipped since it is from previous bucket.
     // Pull should not be called.
     valueProducer.onConditionChanged(true, bucketStartTimeNs);
-    EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
 }
 
 TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-                event->write(tagId);
-                event->write(100);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
     valueProducer->mHasGlobalBase = false;
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 1);
     valueProducer->mHasGlobalBase = true;
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(100, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 }
 
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) {
+/*
+ * Tests that a bucket is marked invalid when a condition change pull fails.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First onConditionChanged
             .WillOnce(Return(false))
             // Second onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(130);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-
-    valueProducer->mCondition = ConditionState::kTrue;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kTrue);
 
     // Bucket start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event->write(1);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
 
     // This will fail and should invalidate the whole bucket since we do not have all the data
@@ -2162,94 +2185,137 @@
 
     // Bucket end.
     allData.clear();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(140);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
 
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
     // Contains base from last pull which was successful.
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(140, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true,
+                                FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
 }
 
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit) {
+/*
+ * Tests that a bucket is marked invalid when the guardrail is hit.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.mutable_dimensions_in_what()->set_field(tagId);
     metric.mutable_dimensions_in_what()->add_child()->set_field(1);
     metric.set_condition(StringToId("SCREEN_ON"));
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 2, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 for (int i = 0; i < 2000; i++) {
-                    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-                    event->write(i);
-                    event->write(i);
-                    event->init();
-                    data->push_back(event);
+                    data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i));
                 }
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 2);
-    EXPECT_EQ(true, valueProducer->mCurrentBucketIsInvalid);
-    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped);
+    ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size());
+
+    // Bucket 2 start.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 10));
+    valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
+
+    // First bucket added to mSkippedBuckets after flush.
+    ASSERT_EQ(1UL, valueProducer->mSkippedBuckets.size());
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+                                true, FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
 }
 
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) {
+/*
+ * Tests that a bucket is marked invalid when the bucket's initial pull fails.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120));
                 return true;
             }))
             // Second onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(130);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-
-    valueProducer->mCondition = ConditionState::kTrue;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kTrue);
 
     // Bucket start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event->write(1);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs);
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
@@ -2257,105 +2323,131 @@
 
     // Bucket end.
     allData.clear();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(140);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
 
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
     // Contains base from last pull which was successful.
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(140, curInterval.base.long_value);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+                                true, FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
 }
 
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) {
+/*
+ * Tests that a bucket is marked invalid when the bucket's final pull fails
+ * (i.e. failed pull on bucket boundary).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(120);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120));
                 return true;
             }))
             // Second onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8);
-                event->write(tagId);
-                event->write(130);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-
-    valueProducer->mCondition = ConditionState::kTrue;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kTrue);
 
     // Bucket start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    event->write(1);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
 
-    // This will fail and should invalidate the whole bucket since we do not have all the data
-    // needed to compute the metric value when the screen was on.
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 3);
 
     // Bucket end.
     allData.clear();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event2->write(1);
-    event2->write(140);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs);
 
     valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
 
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
-    // Last pull failed so based has been reset.
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(0UL, valueProducer->mPastBuckets.size());
+    // Last pull failed so base has been reset.
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+                                true, FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis());
 }
 
 TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
             // Start bucket.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
                 return true;
             }));
 
@@ -2365,62 +2457,59 @@
     // Bucket 2 start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(110);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
 
     // Bucket 3 empty.
     allData.clear();
-    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    event2->init();
-    allData.push_back(event2);
+    allData.push_back(CreateNoValuesLogEvent(tagId, bucket3StartTimeNs + 1));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // Data has been trimmed.
-    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
 }
 
 TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 
     // Empty pull.
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
 }
@@ -2429,46 +2518,42 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 11);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(2);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 2));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 12);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(5);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 5));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 11);
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 12);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 
@@ -2476,16 +2561,18 @@
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     // Data is empty, base should be reset.
-    EXPECT_EQ(false, curInterval.hasBase);
-    EXPECT_EQ(5, curInterval.base.long_value);
+    EXPECT_EQ(false, curBaseInfo.hasBase);
+    EXPECT_EQ(5, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1});
+    ASSERT_EQ(1UL, valueProducer->mPastBuckets.size());
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) {
@@ -2495,165 +2582,149 @@
     metric.set_condition(StringToId("SCREEN_ON"));
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _))
             // First onConditionChanged
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(1);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
 
     // End of bucket
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(2);
-    event->write(2);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 2));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Key 1 should be reset since in not present in the most pull.
-    EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
     auto iterator = valueProducer->mCurrentSlicedBucket.begin();
-    EXPECT_EQ(true, iterator->second[0].hasBase);
-    EXPECT_EQ(2, iterator->second[0].base.long_value);
+    auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin();
+    EXPECT_EQ(true, baseInfoIter->second[0].hasBase);
+    EXPECT_EQ(2, baseInfoIter->second[0].base.long_value);
     EXPECT_EQ(false, iterator->second[0].hasValue);
     iterator++;
-    EXPECT_EQ(false, iterator->second[0].hasBase);
-    EXPECT_EQ(1, iterator->second[0].base.long_value);
+    baseInfoIter++;
+    EXPECT_EQ(false, baseInfoIter->second[0].hasBase);
+    EXPECT_EQ(1, baseInfoIter->second[0].base.long_value);
     EXPECT_EQ(false, iterator->second[0].hasValue);
 
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 }
 
-TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
-    metric.mutable_dimensions_in_what()->set_field(tagId);
-    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
-    metric.set_condition(StringToId("SCREEN_ON"));
-
-    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
-            // Second onConditionChanged.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
-                data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(2);
-                event->write(2);
-                event->init();
-                data->push_back(event);
-                return true;
-            }));
-
-    sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kUnknown;
-
-    valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
-    valueProducer->onConditionChanged(true, bucketStartTimeNs + 20);
-
-    // End of bucket
-    vector<shared_ptr<LogEvent>> allData;
-    allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(4);
-    event->write(4);
-    event->init();
-    allData.push_back(event);
-    valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-
-    // Bucket is incomplete so it is mark as invalid, however the base is fine since the last pull
-    // succeeded.
-    EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
-}
-
-TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2;
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // Initialization.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }))
             // notifyAppUpgrade.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](
+                                     int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                     vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(
-                        bucketStartTimeNs + bucketSizeNs / 2, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
     ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
 
-    valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
     ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size());
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 1, 4));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4));
+    // Pull fails and arrives late.
     valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1);
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+    ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size());
+    EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+    EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason);
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+    EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
     ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
 }
 
 TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // Second onConditionChanged.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10);
                 data->clear();
-                data->push_back(
-                        ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 10, 5));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5));
                 return true;
             }))
             // Third onConditionChanged.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 10);
                 data->clear();
-                data->push_back(
-                        ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 10, 7));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 10, 7));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kUnknown;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kUnknown);
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs);
     ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
 
     // End of first bucket
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 4));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 4));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1);
     ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
 
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(true, curInterval.hasBase);
-    EXPECT_EQ(5, curInterval.base.long_value);
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(true, curBaseInfo.hasBase);
+    EXPECT_EQ(5, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
 
     valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) {
@@ -2665,26 +2736,28 @@
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30);
 
     allData.clear();
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
             // Initialization.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }));
 
@@ -2692,148 +2765,160 @@
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30);
 
     allData.clear();
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
-TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // Initialization.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }))
             // notifyAppUpgrade.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](
+                                     int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                     vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs);
                 data->clear();
-                data->push_back(
-                        ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 2, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First on condition changed.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }))
             // Second on condition changed.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 3));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
-    valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 12);
 
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(2, curInterval.value.long_value);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 10));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs},
+                                    {bucket2StartTimeNs});
 }
 
+// TODO: b/145705635 fix or delete this test
 TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // First condition change.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1));
                 return true;
             }))
             // 2nd condition change.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 8);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1));
                 return true;
             }))
             // 3rd condition change.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 3, 10));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 3, 10));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs + 3);
 
     allData.clear();
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs);
 
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 8);
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10);
 
     allData.clear();
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 30));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 30));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // There was not global base available so all buckets are invalid.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
-}
-
-static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
-    vector<uint8_t> bytes;
-    bytes.resize(proto->size());
-    size_t pos = 0;
-    sp<ProtoReader> reader = proto->data();
-    while (reader->readBuffer() != NULL) {
-        size_t toRead = reader->currentToRead();
-        std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
-        pos += toRead;
-        reader->move(toRead);
-    }
-
-    StatsLogReport report;
-    report.ParseFromArray(bytes.data(), bytes.size());
-    return report;
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
 }
 
 TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
@@ -2843,40 +2928,35 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return());
 
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
             // Initial pull.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(1);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1));
                 return true;
             }));
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
 
     ProtoOutputStream output;
     std::set<string> strSet;
-    valueProducer.onDumpReport(bucketStartTimeNs + 10,
-                               true /* include recent buckets */, true,
+    valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true,
                                FAST, &strSet, &output);
 
     StatsLogReport report = outputStreamToProto(&output);
     // Bucket is invalid since we did not pull when dump report was called.
-    EXPECT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(0, report.value_metrics().data_size());
 }
 
 TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
@@ -2886,51 +2966,41 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return());
 
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _))
             // Initial pull.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(1);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1));
                 return true;
             }));
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
-    event->write(2);
-    event->write(2);
-    event->init();
-    allData.push_back(event);
+    allData.push_back(CreateThreeValueLogEvent(tagId, bucket2StartTimeNs + 1, tagId, 2, 2));
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     ProtoOutputStream output;
     std::set<string> strSet;
-    valueProducer.onDumpReport(bucket4StartTimeNs,
-                               false /* include recent buckets */, true,
-                               FAST, &strSet, &output);
+    valueProducer.onDumpReport(bucket4StartTimeNs, false /* include recent buckets */, true, FAST,
+                               &strSet, &output);
 
     StatsLogReport report = outputStreamToProto(&output);
     // Previous bucket is part of the report.
-    EXPECT_EQ(1, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().data_size());
     EXPECT_EQ(0, report.value_metrics().data(0).bucket_info(0).bucket_num());
 }
 
 TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) {
-    ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); 
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
     SimpleAtomMatcher atomMatcher;
@@ -2940,46 +3010,40 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
-    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+    EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
+    EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return());
 
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // Initial pull.
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
-                event->write(tagId);
-                event->write(1);
-                event->write(1);
-                event->init();
-                data->push_back(event);
+                data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1));
                 return true;
             }))
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(tagId);
-                event->write(3);
-                event->write(3);
-                event->init();
-                data->push_back(event);
+                data->push_back(
+                        CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10, tagId, 3, 3));
                 return true;
             }));
 
-    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex,
                                       eventMatcherWizard, tagId, bucketStartTimeNs,
                                       bucketStartTimeNs, pullerManager);
     valueProducer.prepareFirstBucket();
 
     ProtoOutputStream output;
     std::set<string> strSet;
-    valueProducer.onDumpReport(bucketStartTimeNs + 10,
-                               true /* include recent buckets */, true,
+    valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true,
                                NO_TIME_CONSTRAINTS, &strSet, &output);
 
     StatsLogReport report = outputStreamToProto(&output);
-    EXPECT_EQ(1, report.value_metrics().data_size());
-    EXPECT_EQ(1, report.value_metrics().data(0).bucket_info_size());
+    ASSERT_EQ(1, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size());
     EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
 }
 
@@ -2992,11 +3056,12 @@
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 10));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) {
@@ -3004,44 +3069,48 @@
     metric.set_use_diff(false);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // condition becomes true
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(
-                        bucketStartTimeNs + 30, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10));
                 return true;
             }))
             // condition becomes false
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(
-                        bucketStartTimeNs + 50, 20));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 20));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 50);
     // has one slice
-    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
 
-
     // Now the alarm is delivered. Condition is off though.
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
 
@@ -3050,29 +3119,31 @@
     metric.set_use_diff(false);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _))
             // condition becomes true
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(
-                        bucketStartTimeNs + 30, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
 
     // Now the alarm is delivered. Condition is off though.
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    EXPECT_EQ(false, curInterval.hasBase);
+    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
 
@@ -3082,16 +3153,16 @@
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     // Now the alarm is delivered. Condition is off though.
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Condition was always false.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
 }
 
 TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) {
@@ -3099,29 +3170,1887 @@
     metric.set_use_diff(false);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
             // condition becomes true
-            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8);
                 data->clear();
-                data->push_back(ValueMetricProducerTestHelper::createEvent(
-                        bucketStartTimeNs + 30, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10));
                 return true;
             }))
             .WillOnce(Return(false));
     sp<ValueMetricProducer> valueProducer =
-            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
-    valueProducer->mCondition = ConditionState::kFalse;
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 50);
 
     // Now the alarm is delivered. Condition is off though.
     vector<shared_ptr<LogEvent>> allData;
-    allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // No buckets, we had a failure.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
+}
+
+/*
+ * Test that DUMP_REPORT_REQUESTED dump reason is logged.
+ *
+ * For the bucket to be marked invalid during a dump report requested,
+ * three things must be true:
+ * - we want to include the current partial bucket
+ * - we need a pull (metric is pulled and condition is true)
+ * - the dump latency must be FAST
+ */
+
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 20, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20, 10));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 20);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true,
+                                FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition
+ * change event (i.e. the condition change occurs in the wrong bucket).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 50, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+    // Bucket boundary pull.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15));
+    valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+    // Late condition change event.
+    valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(1, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(1);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate
+ * event (i.e. the accumulate events call occurs in the wrong bucket).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 100);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 100, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+    // Bucket boundary pull.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15));
+    valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+    allData.clear();
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 20));
+
+    // Late accumulateEvents event.
+    valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(1, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition
+ * when a metric is initialized.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10000);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 100, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kUnknown);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that PULL_FAILED dump reason is logged due to a pull failure in
+ * #pullAndMatchEventsLocked.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10));
+                return true;
+            }))
+            // Dump report requested, pull fails.
+            .WillOnce(Return(false));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event
+ * skips over more than one bucket.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket4StartTimeNs + 10);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1000, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+    // Condition change event that skips forward by three buckets.
+    valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10);
+
+    int64_t dumpTimeNs = bucket4StartTimeNs + 1000;
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(2, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket4StartTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis());
+
+    // This bucket is skipped because a dumpReport with include current buckets is called.
+    // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data
+    // since the condition is false for the entire bucket interval.
+    EXPECT_EQ(NanoToMillis(bucket4StartTimeNs),
+              report.value_metrics().skipped(1).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpTimeNs),
+              report.value_metrics().skipped(1).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size());
+
+    dropEvent = report.value_metrics().skipped(1).drop_event(0);
+    EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size
+ * is smaller than the "min_bucket_size_nanos" specified in the metric config.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+    metric.set_min_bucket_size_nanos(10000000000);  // 10 seconds
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 9000000);
+                data->clear();
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 9000000, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that NO_DATA dump reason is logged when a flushed bucket contains no data.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kFalse);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that all buckets are dropped due to condition unknown until the first onConditionChanged.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(
+                        tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(
+                        tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kUnknown);
+
+    // Bucket should be dropped because of condition unknown.
+    int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC;
+    valueProducer->notifyAppUpgrade(appUpgradeTimeNs);
+
+    // Bucket also dropped due to condition unknown
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3));
+    valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
+
+    // This bucket is also dropped due to condition unknown.
+    int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC;
+    valueProducer->onConditionChanged(true, conditionChangeTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(3, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(appUpgradeTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis());
+
+    EXPECT_EQ(NanoToMillis(appUpgradeTimeNs),
+              report.value_metrics().skipped(1).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(1).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size());
+
+    dropEvent = report.value_metrics().skipped(1).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis());
+
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(2).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(2).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size());
+
+    dropEvent = report.value_metrics().skipped(2).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket
+ * was not flushed in time.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10));
+                return true;
+            }))
+            // App Update.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1000);
+                data->clear();
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1000, 15));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric,
+                                                                            ConditionState::kFalse);
+
+    // Condition changed event
+    int64_t conditionChangeTimeNs = bucketStartTimeNs + 10;
+    valueProducer->onConditionChanged(true, conditionChangeTimeNs);
+
+    // App update event.
+    int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000;
+    valueProducer->notifyAppUpgrade(appUpdateTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds
+    valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(1, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size());
+    auto data = report.value_metrics().data(0);
+    ASSERT_EQ(0, data.bucket_info(0).bucket_num());
+    EXPECT_EQ(5, data.bucket_info(0).values(0).value_long());
+
+    EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test multiple bucket drop events in the same bucket.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _))
+            // Condition change to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kUnknown);
+
+    // Condition change event.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 1000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true,
+                                FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(1);
+    EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that the number of logged bucket drop events is capped at the maximum.
+ * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached().
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // First condition change event.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+                for (int i = 0; i < 2000; i++) {
+                    data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i));
+                }
+                return true;
+            }))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Return(false))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 220);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 220, 10));
+                return true;
+            }));
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kUnknown);
+
+    // First condition change event causes guardrail to be reached.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+    // 2-10 condition change events result in failed pulls.
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 30);
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 70);
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 90);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 100);
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 150);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 170);
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 190);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 200);
+
+    // Condition change event 11
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 220);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 1000;
+    // Because we already have 10 dump events in the current bucket,
+    // this case should not be added to the list of dump events.
+    valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true,
+                                FAST /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(10, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(1);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(2);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(3);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(4);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(5);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(6);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(7);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(8);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis());
+
+    dropEvent = report.value_metrics().skipped(0).drop_event(9);
+    EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test metric with a simple sliced state
+ * - Increasing values
+ * - Using diff
+ * - Second field is value field
+ */
+TEST(ValueMetricProducerTest, TestSlicedState) {
+    // Set up ValueMetricProducer.
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE");
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // ValueMetricProducer initialized.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
+                return true;
+            }))
+            // Screen state change to ON.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
+                return true;
+            }))
+            // Screen state change to OFF.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9));
+                return true;
+            }))
+            // Screen state change to ON.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30));
+                return true;
+            }));
+
+    StateManager::getInstance().clear();
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithState(
+                    pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {});
+    EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+    // Set up StateManager and check that StateTrackers are initialized.
+    StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer);
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    // Bucket status after metric initialized.
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    auto it = valueProducer->mCurrentSlicedBucket.begin();
+    auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(3, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after screen state change kStateUnknown->ON.
+    auto screenEvent = CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change ON->OFF.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(9, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, ON}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(4, it->second[0].value.long_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    it++;
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change OFF->ON.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(21, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, OFF}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(12, it->second[0].value.long_value);
+    // Value for dimension, state key {{}, ON}
+    it++;
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(4, it->second[0].value.long_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    it++;
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Start dump report and check output.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(3, report.value_metrics().data_size());
+
+    auto data = report.value_metrics().data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(1);
+    ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
+    EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(2);
+    ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size());
+    EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+}
+
+/*
+ * Test metric with sliced state with map
+ * - Increasing values
+ * - Using diff
+ * - Second field is value field
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
+    // Set up ValueMetricProducer.
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE_ONOFF");
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // ValueMetricProducer initialized.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
+                return true;
+            }))
+            // Screen state change to ON.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
+                return true;
+            }))
+            // Screen state change to VR has no pull because it is in the same
+            // state group as ON.
+
+            // Screen state change to ON has no pull because it is in the same
+            // state group as VR.
+
+            // Screen state change to OFF.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21));
+                return true;
+            }))
+            // Dump report requested.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30));
+                return true;
+            }));
+
+    const StateMap& stateMap =
+            CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123);
+    const StateMap_StateGroup screenOnGroup = stateMap.group(0);
+    const StateMap_StateGroup screenOffGroup = stateMap.group(1);
+
+    unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+    for (auto group : stateMap.group()) {
+        for (auto value : group.value()) {
+            stateGroupMap[SCREEN_STATE_ATOM_ID][value] = group.group_id();
+        }
+    }
+
+    StateManager::getInstance().clear();
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithState(
+                    pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap);
+
+    // Set up StateManager and check that StateTrackers are initialized.
+    StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer);
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    // Bucket status after metric initialized.
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    auto it = valueProducer->mCurrentSlicedBucket.begin();
+    auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(3, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, {kStateUnknown}}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after screen state change kStateUnknown->ON.
+    auto screenEvent = CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(screenOnGroup.group_id(),
+              itBase->second[0].currentState.getValues()[0].mValue.long_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change ON->VR.
+    // Both ON and VR are in the same state group, so the base should not change.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_VR);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(screenOnGroup.group_id(),
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change VR->ON.
+    // Both ON and VR are in the same state group, so the base should not change.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(screenOnGroup.group_id(),
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change VR->OFF.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(21, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(screenOffGroup.group_id(),
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{}, ON GROUP}
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(screenOnGroup.group_id(),
+              it->first.getStateValuesKey().getValues()[0].mValue.long_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(16, it->second[0].value.long_value);
+    // Value for dimension, state key {{}, kStateUnknown}
+    it++;
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Start dump report and check output.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(3, report.value_metrics().data_size());
+
+    auto data = report.value_metrics().data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(1);
+    ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
+    EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id());
+
+    data = report.value_metrics().data(2);
+    ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size());
+    EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id());
+}
+
+/*
+ * Test metric that slices by state with a primary field and has dimensions
+ * - Increasing values
+ * - Using diff
+ * - Second field is value field
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
+    // Set up ValueMetricProducer.
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("UID_PROCESS_STATE");
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    MetricStateLink* stateLink = metric.add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // ValueMetricProducer initialized.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+                data->clear();
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 2 /*uid*/, 7));
+                data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1 /*uid*/, 3));
+                return true;
+            }))
+            // Uid 1 process state change from kStateUnknown -> Foreground
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20);
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6));
+
+                // This event should be skipped.
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8));
+                return true;
+            }))
+            // Uid 2 process state change from kStateUnknown -> Background
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40);
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9));
+
+                // This event should be skipped.
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12));
+                return true;
+            }))
+            // Uid 1 process state change from Foreground -> Background
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20);
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13));
+
+                // This event should be skipped.
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11));
+                return true;
+            }))
+            // Uid 1 process state change from Background -> Foreground
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40);
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17));
+
+                // This event should be skipped.
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15));
+                return true;
+            }))
+            // Dump report pull.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50);
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21));
+                return true;
+            }));
+
+    StateManager::getInstance().clear();
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithState(
+                    pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {});
+
+    // Set up StateManager and check that StateTrackers are initialized.
+    StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer);
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Bucket status after metric initialized.
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {uid 1}.
+    auto it = valueProducer->mCurrentSlicedBucket.begin();
+    auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(3, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{uid 1}, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+    // Base for dimension key {uid 2}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(7, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for dimension, state key {{uid 2}, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after uid 1 process state change kStateUnknown -> Foreground.
+    auto uidProcessEvent = CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+    StateManager::getInstance().onLogEvent(*uidProcessEvent);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {uid 1}.
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(6, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 1, kStateUnknown}.
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(3, it->second[0].value.long_value);
+
+    // Base for dimension key {uid 2}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(7, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 2, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after uid 2 process state change kStateUnknown -> Background.
+    uidProcessEvent = CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+    StateManager::getInstance().onLogEvent(*uidProcessEvent);
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {uid 1}.
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(6, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 1, kStateUnknown}.
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(3, it->second[0].value.long_value);
+
+    // Base for dimension key {uid 2}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(9, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 2, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Pull at end of first bucket.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 10));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 15));
+    valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+    // Buckets flushed after end of first bucket.
+    // None of the buckets should have a value.
+    ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(4UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
+    // Base for dimension key {uid 2}.
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(15, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 2, BACKGROUND}.
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Base for dimension key {uid 1}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(10, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 1, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Value for key {uid 1, FOREGROUND}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Value for key {uid 2, kStateUnknown}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after uid 1 process state change from Foreground -> Background.
+    uidProcessEvent = CreateUidProcessStateChangedEvent(
+            bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+    StateManager::getInstance().onLogEvent(*uidProcessEvent);
+
+    ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(4UL, valueProducer->mPastBuckets.size());
+    ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
+    // Base for dimension key {uid 2}.
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(15, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 2, BACKGROUND}.
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+    // Base for dimension key {uid 1}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(13, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 1, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+    // Value for key {uid 1, FOREGROUND}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(3, it->second[0].value.long_value);
+    // Value for key {uid 2, kStateUnknown}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after uid 1 process state change Background->Foreground.
+    uidProcessEvent = CreateUidProcessStateChangedEvent(
+            bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+    StateManager::getInstance().onLogEvent(*uidProcessEvent);
+
+    ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
+    // Base for dimension key {uid 2}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(15, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 2, BACKGROUND}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Base for dimension key {uid 1}
+    it++;
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(17, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {uid 1, kStateUnknown}
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Value for key {uid 1, BACKGROUND}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(4, it->second[0].value.long_value);
+
+    // Value for key {uid 1, FOREGROUND}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(3, it->second[0].value.long_value);
+
+    // Value for key {uid 2, kStateUnknown}
+    it++;
+    ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+    EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+
+    // Start dump report and check output.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true,
+                                NO_TIME_CONSTRAINTS, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(5, report.value_metrics().data_size());
+
+    auto data = report.value_metrics().data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(1);
+    ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
+    EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(2);
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size());
+    EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long());
+
+    data = report.value_metrics().data(3);
+    ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size());
+    EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+
+    data = report.value_metrics().data(4);
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size());
+    EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long());
+    EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long());
+}
+
+TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
+    // Set up ValueMetricProducer.
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState(
+            "BATTERY_SAVER_MODE_STATE");
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _))
+            // Condition changed to true.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC);
+                data->clear();
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3));
+                return true;
+            }))
+            // Battery saver mode state changed to OFF.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC);
+                data->clear();
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5));
+                return true;
+            }))
+            // Condition changed to false.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+                EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC);
+                data->clear();
+                data->push_back(CreateRepeatedValueLogEvent(
+                        tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15));
+                return true;
+            }));
+
+    StateManager::getInstance().clear();
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithConditionAndState(
+                    pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {},
+                    ConditionState::kFalse);
+    EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+    // Set up StateManager and check that StateTrackers are initialized.
+    StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED,
+                                                 valueProducer);
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+                         util::BATTERY_SAVER_MODE_STATE_CHANGED));
+
+    // Bucket status after battery saver mode ON event.
+    // Condition is false so we do nothing.
+    unique_ptr<LogEvent> batterySaverOnEvent =
+            CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC);
+    StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+    EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+    EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size());
+
+    // Bucket status after condition change to true.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC);
+    // Base for dimension key {}
+    ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+    std::unordered_map<HashableDimensionKey, std::vector<ValueMetricProducer::BaseInfo>>::iterator
+            itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(3, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::ON,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {{}, -1}
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    std::unordered_map<MetricDimensionKey, std::vector<ValueMetricProducer::Interval>>::iterator
+            it = valueProducer->mCurrentSlicedBucket.begin();
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_FALSE(it->second[0].hasValue);
+
+    // Bucket status after battery saver mode OFF event.
+    unique_ptr<LogEvent> batterySaverOffEvent =
+            CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC);
+    StateManager::getInstance().onLogEvent(*batterySaverOffEvent);
+    // Base for dimension key {}
+    ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+    itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {{}, ON}
+    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::ON,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Pull at end of first bucket.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11));
+    valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
+
+    EXPECT_EQ(2UL, valueProducer->mPastBuckets.size());
+    EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+    itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
+    EXPECT_TRUE(itBase->second[0].hasBase);
+    EXPECT_EQ(11, itBase->second[0].base.long_value);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+
+    // Bucket 2 status after condition change to false.
+    valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC);
+    // Base for dimension key {}
+    ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+    itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
+    EXPECT_FALSE(itBase->second[0].hasBase);
+    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+    // Value for key {{}, OFF}
+    ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+    ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+    EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+              it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+    EXPECT_TRUE(it->second[0].hasValue);
+    EXPECT_EQ(4, it->second[0].value.long_value);
+
+    // Start dump report and check output.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC,
+                                true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+                                &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(2, report.value_metrics().data_size());
+
+    ValueMetricData data = report.value_metrics().data(0);
+    EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value());
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).values(0).value_long());
+
+    data = report.value_metrics().data(1);
+    EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value());
+    ASSERT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(6, data.bucket_info(0).values(0).value_long());
+    EXPECT_EQ(4, data.bucket_info(1).values(0).value_long());
+}
+
+/*
+ * Test bucket splits when condition is unknown.
+ */
+TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric,
+                    ConditionState::kUnknown);
+
+    // App update event.
+    int64_t appUpdateTimeNs = bucketStartTimeNs + 1000;
+    valueProducer->notifyAppUpgrade(appUpdateTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds
+    valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
index 7b9c0d6..108df04b 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -26,10 +26,23 @@
     return dimension;
 }
 
+HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value) {
+    HashableDimensionKey dimension;
+    int pos[] = {key, 0, 0};
+    dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value)));
+
+    return dimension;
+}
+
 MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) {
     return MetricDimensionKey(getMockedDimensionKey(tagId, key, value), DEFAULT_DIMENSION_KEY);
 }
 
+MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value) {
+    return MetricDimensionKey(DEFAULT_DIMENSION_KEY,
+                              getMockedDimensionKeyLongValue(tagId, key, value));
+}
+
 void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) {
     matcher->set_field(tagId);
 }
@@ -41,4 +54,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 97c1072..eeb38a4 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -26,33 +26,39 @@
 
 class MockConditionWizard : public ConditionWizard {
 public:
-    MOCK_METHOD6(query,
+    MOCK_METHOD3(query,
                  ConditionState(const int conditionIndex, const ConditionKey& conditionParameters,
-                                const vector<Matcher>& dimensionFields,
-                                const bool isSubsetDim, const bool isPartialLink,
-                                std::unordered_set<HashableDimensionKey>* dimensionKeySet));
+                                const bool isPartialLink));
 };
 
 class MockStatsPullerManager : public StatsPullerManager {
 public:
-    MOCK_METHOD4(RegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver,
-                                        int64_t nextPulltimeNs, int64_t intervalNs));
-    MOCK_METHOD2(UnRegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver));
-    MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
-};
-
-class MockUidMap : public UidMap {
- public:
-  MOCK_CONST_METHOD1(getHostUidOrSelf, int(int uid));
+    MOCK_METHOD5(RegisterReceiver,
+                 void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver,
+                      int64_t nextPulltimeNs, int64_t intervalNs));
+    MOCK_METHOD3(UnRegisterReceiver,
+                 void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver));
+    MOCK_METHOD5(Pull, bool(const int pullCode, const ConfigKey& key, const int64_t eventTimeNs,
+                            vector<std::shared_ptr<LogEvent>>* data, bool useUids));
+    MOCK_METHOD5(Pull,
+                 bool(const int pullCode, const vector<int32_t>& uids, const int64_t eventTimeNs,
+                      vector<std::shared_ptr<LogEvent>>* data, bool useUids));
+    MOCK_METHOD2(RegisterPullUidProvider,
+                 void(const ConfigKey& configKey, wp<PullUidProvider> provider));
+    MOCK_METHOD2(UnregisterPullUidProvider,
+                 void(const ConfigKey& configKey, wp<PullUidProvider> provider));
 };
 
 HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
 MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value);
 
+HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value);
+MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value);
+
 // Utils to build FieldMatcher proto for simple one-depth atoms.
 void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher);
 void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher);
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index 73d1fd7..e384b6a 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -12,17 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gtest/gtest.h>
+#include "src/shell/ShellSubscriber.h"
 
+#include <gtest/gtest.h>
+#include <stdio.h>
 #include <unistd.h>
+
+#include <vector>
+
 #include "frameworks/base/cmds/statsd/src/atoms.pb.h"
 #include "frameworks/base/cmds/statsd/src/shell/shell_config.pb.h"
 #include "frameworks/base/cmds/statsd/src/shell/shell_data.pb.h"
-#include "src/shell/ShellSubscriber.h"
+#include "stats_event.h"
 #include "tests/metrics/metrics_test_helper.h"
-
-#include <stdio.h>
-#include <vector>
+#include "tests/statsd_test_util.h"
 
 using namespace android::os::statsd;
 using android::sp;
@@ -34,27 +37,6 @@
 
 #ifdef __ANDROID__
 
-class MyResultReceiver : public BnResultReceiver {
-public:
-    Mutex mMutex;
-    Condition mCondition;
-    bool mHaveResult = false;
-    int32_t mResult = 0;
-
-    virtual void send(int32_t resultCode) {
-        AutoMutex _l(mMutex);
-        mResult = resultCode;
-        mHaveResult = true;
-        mCondition.signal();
-    }
-
-    int32_t waitForResult() {
-        AutoMutex _l(mMutex);
-        mCondition.waitRelative(mMutex, 1000000000);
-        return mResult;
-    }
-};
-
 void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
                   sp<MockStatsPullerManager> pullerManager,
                   const vector<std::shared_ptr<LogEvent>>& pushedEvents,
@@ -67,10 +49,7 @@
     ASSERT_EQ(0, pipe(fds_data));
 
     size_t bufferSize = config.ByteSize();
-
     // write the config to pipe, first write size of the config
-    vector<uint8_t> size_buffer(sizeof(bufferSize));
-    std::memcpy(size_buffer.data(), &bufferSize, sizeof(bufferSize));
     write(fds_config[1], &bufferSize, sizeof(bufferSize));
     // then write config itself
     vector<uint8_t> buffer(bufferSize);
@@ -79,11 +58,10 @@
     close(fds_config[1]);
 
     sp<ShellSubscriber> shellClient = new ShellSubscriber(uidMap, pullerManager);
-    sp<MyResultReceiver> resultReceiver = new MyResultReceiver();
 
     // mimic a binder thread that a shell subscriber runs on. it would block.
-    std::thread reader([&resultReceiver, &fds_config, &fds_data, &shellClient] {
-        shellClient->startNewSubscription(fds_config[0], fds_data[1], resultReceiver, -1);
+    std::thread reader([&shellClient, &fds_config, &fds_data] {
+        shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1);
     });
     reader.detach();
 
@@ -108,26 +86,37 @@
     // wait for the data to be written.
     std::this_thread::sleep_for(100ms);
 
-    int expected_data_size = expectedData.ByteSize();
+    // Because we might receive heartbeats from statsd, consisting of data sizes
+    // of 0, encapsulate reads within a while loop.
+    bool readAtom = false;
+    while (!readAtom) {
+        // Read the atom size.
+        size_t dataSize = 0;
+        read(fds_data[0], &dataSize, sizeof(dataSize));
+        if (dataSize == 0) continue;
+        EXPECT_EQ(expectedData.ByteSize(), int(dataSize));
 
-    // now read from the pipe. firstly read the atom size.
-    size_t dataSize = 0;
-    EXPECT_EQ((int)sizeof(dataSize), read(fds_data[0], &dataSize, sizeof(dataSize)));
-    EXPECT_EQ(expected_data_size, (int)dataSize);
+        // Read that much data in proto binary format.
+        vector<uint8_t> dataBuffer(dataSize);
+        EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize));
 
-    // then read that much data which is the atom in proto binary format
-    vector<uint8_t> dataBuffer(dataSize);
-    EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize));
+        // Make sure the received bytes can be parsed to an atom.
+        ShellData receivedAtom;
+        EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
 
-    // make sure the received bytes can be parsed to an atom
-    ShellData receivedAtom;
-    EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
+        // Serialize the expected atom to byte array and compare to make sure
+        // they are the same.
+        vector<uint8_t> expectedAtomBuffer(expectedData.ByteSize());
+        expectedData.SerializeToArray(expectedAtomBuffer.data(), expectedData.ByteSize());
+        EXPECT_EQ(expectedAtomBuffer, dataBuffer);
 
-    // serialze the expected atom to bytes. and compare. to make sure they are the same.
-    vector<uint8_t> atomBuffer(expected_data_size);
-    expectedData.SerializeToArray(&atomBuffer[0], expected_data_size);
-    EXPECT_EQ(atomBuffer, dataBuffer);
+        readAtom = true;
+    }
+
     close(fds_data[0]);
+    if (reader.joinable()) {
+        reader.join();
+    }
 }
 
 TEST(ShellSubscriberTest, testPushedSubscription) {
@@ -136,11 +125,10 @@
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     vector<std::shared_ptr<LogEvent>> pushedList;
 
-    std::shared_ptr<LogEvent> event1 =
-            std::make_shared<LogEvent>(29 /*screen_state_atom_id*/, 1000 /*timestamp*/);
-    event1->write(::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
-    event1->init();
-    pushedList.push_back(event1);
+    // Create the LogEvent from an AStatsEvent
+    std::unique_ptr<LogEvent> logEvent = CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    pushedList.push_back(std::move(logEvent));
 
     // create a simple config to get screen events
     ShellSubscription config;
@@ -183,29 +171,33 @@
     return config;
 }
 
+shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 10016);
+    AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeInt64(statsEvent, timeMillis);
+
+    std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
 }  // namespace
 
 TEST(ShellSubscriberTest, testPulledSubscription) {
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    EXPECT_CALL(*pullerManager, Pull(10016, _))
-            .WillRepeatedly(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+    const vector<int32_t> uids = {AID_SYSTEM};
+    EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _, _))
+            .WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t,
+                                      vector<std::shared_ptr<LogEvent>>* data, bool) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, 1111L);
-                event->write(kUid1);
-                event->write(kCpuTime1);
-                event->init();
-                data->push_back(event);
-                // another event
-                event = make_shared<LogEvent>(tagId, 1111L);
-                event->write(kUid2);
-                event->write(kCpuTime2);
-                event->init();
-                data->push_back(event);
+                data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1));
+                data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2));
                 return true;
             }));
-
     runShellTest(getPulledConfig(), uidMap, pullerManager, vector<std::shared_ptr<LogEvent>>(),
                  getExpectedShellData());
 }
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
new file mode 100644
index 0000000..6516c15
--- /dev/null
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2019, 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 "state/StateTracker.h"
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+
+#include "state/StateListener.h"
+#include "state/StateManager.h"
+#include "state/StateTracker.h"
+#include "stats_event.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const int32_t timestampNs = 1000;
+
+/**
+ * Mock StateListener class for testing.
+ * Stores primary key and state pairs.
+ */
+class TestStateListener : public virtual StateListener {
+public:
+    TestStateListener(){};
+
+    virtual ~TestStateListener(){};
+
+    struct Update {
+        Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){};
+        HashableDimensionKey mKey;
+        int mState;
+    };
+
+    std::vector<Update> updates;
+
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) {
+        updates.emplace_back(primaryKey, newState.mValue.int_value);
+    }
+};
+
+int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& queryKey) {
+    FieldValue output;
+    mgr.getStateValue(atomId, queryKey, &output);
+    return output.mValue.int_value;
+}
+
+// START: build event functions.
+// Incorrect event - missing fields
+std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
+                                                     int state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, 1000);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, packageName.c_str());
+    // Missing field 3 - using_alert_window.
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+// Incorrect event - exclusive state has wrong type
+std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::string& packageName) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, 1000);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, packageName.c_str());
+    AStatsEvent_writeInt32(statsEvent, true);       // using_alert_window
+    AStatsEvent_writeString(statsEvent, "string");  // exclusive state: string instead of int
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+// END: build event functions.
+
+TEST(StateListenerTest, TestStateListenerWeakPointer) {
+    sp<TestStateListener> listener = new TestStateListener();
+    wp<TestStateListener> wListener = listener;
+    listener = nullptr;  // let go of listener
+    EXPECT_TRUE(wListener.promote() == nullptr);
+}
+
+TEST(StateManagerTest, TestStateManagerGetInstance) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager& mgr = StateManager::getInstance();
+    mgr.clear();
+
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+}
+
+TEST(StateManagerTest, TestOnLogEvent) {
+    sp<MockUidMap> uidMap = makeMockUidMapForPackage("com.android.systemui", {10111});
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.updateLogSources(uidMap);
+    // Add StateTracker by registering a listener.
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+
+    // log event using AID_ROOT
+    std::unique_ptr<LogEvent> event = CreateScreenStateChangedEvent(
+            timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    mgr.onLogEvent(*event);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey));
+
+    // log event using mocked uid
+    event = CreateScreenStateChangedEvent(
+            timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF, 10111);
+    mgr.onLogEvent(*event);
+
+    // check StateTracker was updated by querying for state
+    queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey));
+
+    // log event using non-whitelisted uid
+    event = CreateScreenStateChangedEvent(timestampNs,
+                                          android::view::DisplayStateEnum::DISPLAY_STATE_ON, 10112);
+    mgr.onLogEvent(*event);
+
+    // check StateTracker was NOT updated by querying for state
+    queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey));
+
+    // log event using AID_SYSTEM
+    event = CreateScreenStateChangedEvent(
+            timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON, AID_SYSTEM);
+    mgr.onLogEvent(*event);
+
+    // check StateTracker was updated by querying for state
+    queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test registering listeners to StateTrackers
+ *
+ * - StateManager will create a new StateTracker if it doesn't already exist
+ * and then register the listener to the StateTracker.
+ * - If a listener is already registered to a StateTracker, it is not added again.
+ * - StateTrackers are only created for atoms that are state atoms.
+ */
+TEST(StateTrackerTest, TestRegisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Register listener to non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Register listener to existing StateTracker
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Register already registered listener to existing StateTracker
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Register listener to non-state atom
+    mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2);
+    EXPECT_EQ(2, mgr.getStateTrackersCount());
+}
+
+/**
+ * Test unregistering listeners from StateTrackers
+ *
+ * - StateManager will unregister listeners from a StateTracker only if the
+ * StateTracker exists and the listener is registered to the StateTracker.
+ * - Once all listeners are removed from a StateTracker, the StateTracker
+ * is also removed.
+ */
+TEST(StateTrackerTest, TestUnregisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Unregister listener from non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Unregister non-registered listener from existing StateTracker
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+    mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Unregister second-to-last listener from StateTracker
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2);
+    mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+
+    // Unregister last listener from StateTracker
+    mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED));
+}
+
+/**
+ * Test a binary state atom with nested counting.
+ *
+ * To go from an "ON" state to an "OFF" state with nested counting, we must see
+ * an equal number of "OFF" events as "ON" events.
+ * For example, ACQUIRE, ACQUIRE, RELEASE will still be in the ACQUIRE state.
+ * ACQUIRE, ACQUIRE, RELEASE, RELEASE will be in the RELEASE state.
+ */
+TEST(StateTrackerTest, TestStateChangeNested) {
+    sp<TestStateListener> listener = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener);
+
+    std::vector<int> attributionUids1 = {1000};
+    std::vector<string> attributionTags1 = {"tag"};
+
+    std::unique_ptr<LogEvent> event1 = CreateAcquireWakelockEvent(timestampNs, attributionUids1,
+                                                                  attributionTags1, "wakelockName");
+    mgr.onLogEvent(*event1);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener->updates[0].mState);
+    listener->updates.clear();
+
+    std::unique_ptr<LogEvent> event2 = CreateAcquireWakelockEvent(
+            timestampNs + 1000, attributionUids1, attributionTags1, "wakelockName");
+    mgr.onLogEvent(*event2);
+    ASSERT_EQ(0, listener->updates.size());
+
+    std::unique_ptr<LogEvent> event3 = CreateReleaseWakelockEvent(
+            timestampNs + 2000, attributionUids1, attributionTags1, "wakelockName");
+    mgr.onLogEvent(*event3);
+    ASSERT_EQ(0, listener->updates.size());
+
+    std::unique_ptr<LogEvent> event4 = CreateReleaseWakelockEvent(
+            timestampNs + 3000, attributionUids1, attributionTags1, "wakelockName");
+    mgr.onLogEvent(*event4);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(0, listener->updates[0].mState);
+}
+
+/**
+ * Test a state atom with a reset state.
+ *
+ * If the reset state value is seen, every state in the map is set to the default
+ * state and every listener is notified.
+ */
+TEST(StateTrackerTest, TestStateChangeReset) {
+    sp<TestStateListener> listener = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::BLE_SCAN_STATE_CHANGED, listener);
+
+    std::vector<int> attributionUids1 = {1000};
+    std::vector<string> attributionTags1 = {"tag1"};
+    std::vector<int> attributionUids2 = {2000};
+
+    std::unique_ptr<LogEvent> event1 =
+            CreateBleScanStateChangedEvent(timestampNs, attributionUids1, attributionTags1,
+                                           BleScanStateChanged::ON, false, false, false);
+    mgr.onLogEvent(*event1);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState);
+    FieldValue stateFieldValue;
+    mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue);
+    EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value);
+    listener->updates.clear();
+
+    std::unique_ptr<LogEvent> event2 =
+            CreateBleScanStateChangedEvent(timestampNs + 1000, attributionUids2, attributionTags1,
+                                           BleScanStateChanged::ON, false, false, false);
+    mgr.onLogEvent(*event2);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState);
+    mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue);
+    EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value);
+    listener->updates.clear();
+
+    std::unique_ptr<LogEvent> event3 =
+            CreateBleScanStateChangedEvent(timestampNs + 2000, attributionUids2, attributionTags1,
+                                           BleScanStateChanged::RESET, false, false, false);
+    mgr.onLogEvent(*event3);
+    ASSERT_EQ(2, listener->updates.size());
+    for (const TestStateListener::Update& update : listener->updates) {
+        EXPECT_EQ(BleScanStateChanged::OFF, update.mState);
+
+        mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, update.mKey, &stateFieldValue);
+        EXPECT_EQ(BleScanStateChanged::OFF, stateFieldValue.mValue.int_value);
+    }
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states without primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+
+    // log event
+    std::unique_ptr<LogEvent> event = CreateScreenStateChangedEvent(
+            timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    ASSERT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey);
+    EXPECT_EQ(2, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with one primary key.
+ */
+TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener1);
+
+    // log event
+    std::unique_ptr<LogEvent> event = CreateUidProcessStateChangedEvent(
+            timestampNs, 1000 /*uid*/, android::app::ProcessStateEnum::PROCESS_STATE_TOP);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    ASSERT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1002, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getUidProcessKey(1000 /* uid */, &queryKey);
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+              getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener1);
+
+    // Log event.
+    std::vector<int> attributionUids = {1001};
+    std::vector<string> attributionTags = {"tag1"};
+
+    std::unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(timestampNs, attributionUids,
+                                                                 attributionTags, "wakelockName");
+    mgr.onLogEvent(*event);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(util::WAKELOCK_STATE_CHANGED));
+
+    // Check listener was updated.
+    ASSERT_EQ(1, listener1->updates.size());
+    ASSERT_EQ(3, listener1->updates[0].mKey.getValues().size());
+    EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value);
+    EXPECT_EQ("wakelockName", listener1->updates[0].mKey.getValues()[2].mValue.str_value);
+    EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState);
+
+    // Check StateTracker was updated by querying for state.
+    HashableDimensionKey queryKey;
+    getPartialWakelockKey(1001 /* uid */, "wakelockName", &queryKey);
+    EXPECT_EQ(WakelockStateChanged::ACQUIRE,
+              getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey));
+
+    // No state stored for this query key.
+    HashableDimensionKey queryKey2;
+    getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2);
+    EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/,
+              getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey2));
+
+    // Partial query fails.
+    HashableDimensionKey queryKey3;
+    getPartialWakelockKey(1001 /* uid */, &queryKey3);
+    EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/,
+              getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey3));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with multiple primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::unique_ptr<LogEvent> event = CreateOverlayStateChangedEvent(
+            timestampNs, 1000 /* uid */, "package1", true /*using_alert_window*/,
+            OverlayStateChanged::ENTERED);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    ASSERT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getOverlayKey(1000 /* uid */, "package1", &queryKey);
+    EXPECT_EQ(OverlayStateChanged::ENTERED,
+              getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged
+ * when there is an error extracting state from log event. Listener is not
+ * updated of state change.
+ */
+TEST(StateTrackerTest, TestStateChangeEventError) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event1 =
+            buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
+    std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
+
+    // check listener was updated
+    mgr.onLogEvent(*event1);
+    ASSERT_EQ(0, listener1->updates.size());
+    mgr.onLogEvent(*event2);
+    ASSERT_EQ(0, listener1->updates.size());
+}
+
+TEST(StateTrackerTest, TestStateQuery) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    sp<TestStateListener> listener3 = new TestStateListener();
+    sp<TestStateListener> listener4 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
+    mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener2);
+    mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener3);
+    mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener4);
+
+    std::unique_ptr<LogEvent> event1 = CreateUidProcessStateChangedEvent(
+            timestampNs, 1000 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    std::unique_ptr<LogEvent> event2 = CreateUidProcessStateChangedEvent(
+            timestampNs + 1000, 1001 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  //  state value:
+                                                                                //  1003
+    std::unique_ptr<LogEvent> event3 = CreateUidProcessStateChangedEvent(
+            timestampNs + 2000, 1002 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  //  state value: 1000
+    std::unique_ptr<LogEvent> event4 = CreateUidProcessStateChangedEvent(
+            timestampNs + 3000, 1001 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    std::unique_ptr<LogEvent> event5 = CreateScreenStateChangedEvent(
+            timestampNs + 4000, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    std::unique_ptr<LogEvent> event6 = CreateOverlayStateChangedEvent(
+            timestampNs + 5000, 1000 /*uid*/, "package1", true /*using_alert_window*/,
+            OverlayStateChanged::ENTERED);
+    std::unique_ptr<LogEvent> event7 = CreateOverlayStateChangedEvent(
+            timestampNs + 6000, 1000 /*uid*/, "package2", true /*using_alert_window*/,
+            OverlayStateChanged::EXITED);
+
+    std::vector<int> attributionUids = {1005};
+    std::vector<string> attributionTags = {"tag"};
+
+    std::unique_ptr<LogEvent> event8 = CreateAcquireWakelockEvent(
+            timestampNs + 7000, attributionUids, attributionTags, "wakelock1");
+    std::unique_ptr<LogEvent> event9 = CreateReleaseWakelockEvent(
+            timestampNs + 8000, attributionUids, attributionTags, "wakelock2");
+
+    mgr.onLogEvent(*event1);
+    mgr.onLogEvent(*event2);
+    mgr.onLogEvent(*event3);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event6);
+    mgr.onLogEvent(*event7);
+    mgr.onLogEvent(*event8);
+    mgr.onLogEvent(*event9);
+
+    // Query for UidProcessState of uid 1001
+    HashableDimensionKey queryKey1;
+    getUidProcessKey(1001, &queryKey1);
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+              getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for UidProcessState of uid 1004 - not in state map
+    HashableDimensionKey queryKey2;
+    getUidProcessKey(1004, &queryKey2);
+    EXPECT_EQ(-1, getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED,
+                              queryKey2));  // default state
+
+    // Query for UidProcessState of uid 1001 - after change in state
+    mgr.onLogEvent(*event4);
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+              getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for ScreenState
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+
+    // Query for OverlayState of uid 1000, package name "package2"
+    HashableDimensionKey queryKey3;
+    getOverlayKey(1000, "package2", &queryKey3);
+    EXPECT_EQ(OverlayStateChanged::EXITED,
+              getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey3));
+
+    // Query for WakelockState of uid 1005, tag 2
+    HashableDimensionKey queryKey4;
+    getPartialWakelockKey(1005, "wakelock2", &queryKey4);
+    EXPECT_EQ(WakelockStateChanged::RELEASE,
+              getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey4));
+
+    // Query for WakelockState of uid 1005, tag 1
+    HashableDimensionKey queryKey5;
+    getPartialWakelockKey(1005, "wakelock1", &queryKey5);
+    EXPECT_EQ(WakelockStateChanged::ACQUIRE,
+              getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey5));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2c4f3c7..cee8372 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -14,10 +14,33 @@
 
 #include "statsd_test_util.h"
 
+#include <aidl/android/util/StatsEventParcel.h>
+#include "stats_event.h"
+
+using aidl::android::util::StatsEventParcel;
+using std::shared_ptr;
+
 namespace android {
 namespace os {
 namespace statsd {
 
+StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
+    vector<uint8_t> bytes;
+    bytes.resize(proto->size());
+    size_t pos = 0;
+    sp<ProtoReader> reader = proto->data();
+
+    while (reader->readBuffer() != NULL) {
+        size_t toRead = reader->currentToRead();
+        std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
+        pos += toRead;
+        reader->move(toRead);
+    }
+
+    StatsLogReport report;
+    report.ParseFromArray(bytes.data(), bytes.size());
+    return report;
+}
 
 AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
     AtomMatcher atom_matcher;
@@ -28,7 +51,7 @@
 }
 
 AtomMatcher CreateTemperatureAtomMatcher() {
-    return CreateSimpleAtomMatcher("TemperatureMatcher", android::util::TEMPERATURE);
+    return CreateSimpleAtomMatcher("TemperatureMatcher", util::TEMPERATURE);
 }
 
 AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
@@ -36,7 +59,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCHEDULED_JOB_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::SCHEDULED_JOB_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(3);  // State field.
     field_value_matcher->set_eq_int(state);
@@ -57,7 +80,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED);
+    simple_atom_matcher->set_atom_id(util::SCREEN_BRIGHTNESS_CHANGED);
     return atom_matcher;
 }
 
@@ -65,7 +88,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId("UidProcessStateChanged"));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::UID_PROCESS_STATE_CHANGED);
     return atom_matcher;
 }
 
@@ -74,7 +97,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::WAKELOCK_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(4);  // State field.
     field_value_matcher->set_eq_int(state);
@@ -94,7 +117,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::BATTERY_SAVER_MODE_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::BATTERY_SAVER_MODE_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(1);  // State field.
     field_value_matcher->set_eq_int(state);
@@ -112,19 +135,39 @@
         "BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
 }
 
-
-AtomMatcher CreateScreenStateChangedAtomMatcher(
-    const string& name, android::view::DisplayStateEnum state) {
+AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name,
+                                                 BatteryPluggedStateEnum state) {
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(1);  // State field.
     field_value_matcher->set_eq_int(state);
     return atom_matcher;
 }
 
+AtomMatcher CreateBatteryStateNoneMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+}
+
+AtomMatcher CreateBatteryStateUsbMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+}
+
+AtomMatcher CreateScreenStateChangedAtomMatcher(
+    const string& name, android::view::DisplayStateEnum state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(util::SCREEN_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(1);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
 
 AtomMatcher CreateScreenTurnedOnAtomMatcher() {
     return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
@@ -141,7 +184,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::SYNC_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(3);  // State field.
     field_value_matcher->set_eq_int(state);
@@ -161,7 +204,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(4);  // Activity field.
     field_value_matcher->set_eq_int(state);
@@ -183,7 +226,7 @@
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId(name));
     auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    simple_atom_matcher->set_atom_id(util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
     field_value_matcher->set_field(3);  // Process state field.
     field_value_matcher->set_eq_int(state);
@@ -211,6 +254,14 @@
     return predicate;
 }
 
+Predicate CreateDeviceUnpluggedPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("DeviceUnplugged"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb"));
+    return predicate;
+}
+
 Predicate CreateScreenIsOnPredicate() {
     Predicate predicate;
     predicate.set_id(StringToId("ScreenIsOn"));
@@ -251,6 +302,95 @@
     return predicate;
 }
 
+State CreateScreenState() {
+    State state;
+    state.set_id(StringToId("ScreenState"));
+    state.set_atom_id(util::SCREEN_STATE_CHANGED);
+    return state;
+}
+
+State CreateUidProcessState() {
+    State state;
+    state.set_id(StringToId("UidProcessState"));
+    state.set_atom_id(util::UID_PROCESS_STATE_CHANGED);
+    return state;
+}
+
+State CreateOverlayState() {
+    State state;
+    state.set_id(StringToId("OverlayState"));
+    state.set_atom_id(util::OVERLAY_STATE_CHANGED);
+    return state;
+}
+
+State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+    State state;
+    state.set_id(StringToId("ScreenStateOnOff"));
+    state.set_atom_id(util::SCREEN_STATE_CHANGED);
+
+    auto map = CreateScreenStateOnOffMap(screenOnId, screenOffId);
+    *state.mutable_map() = map;
+
+    return state;
+}
+
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+    State state;
+    state.set_id(StringToId("ScreenStateSimpleOnOff"));
+    state.set_atom_id(util::SCREEN_STATE_CHANGED);
+
+    auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId);
+    *state.mutable_map() = map;
+
+    return state;
+}
+
+StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) {
+    StateMap_StateGroup group;
+    group.set_group_id(screenOnId);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND);
+    return group;
+}
+
+StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) {
+    StateMap_StateGroup group;
+    group.set_group_id(screenOffId);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND);
+    return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) {
+    StateMap_StateGroup group;
+    group.set_group_id(screenOnId);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) {
+    StateMap_StateGroup group;
+    group.set_group_id(screenOffId);
+    group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    return group;
+}
+
+StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+    StateMap map;
+    *map.add_group() = CreateScreenStateOnGroup(screenOnId);
+    *map.add_group() = CreateScreenStateOffGroup(screenOffId);
+    return map;
+}
+
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+    StateMap map;
+    *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId);
+    *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId);
+    return map;
+}
+
 void addPredicateToPredicateCombination(const Predicate& predicate,
                                         Predicate* combinationPredicate) {
     combinationPredicate->mutable_combination()->add_predicate(predicate.id());
@@ -292,173 +432,551 @@
     return dimensions;
 }
 
-std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-    const android::view::DisplayStateEnum state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs);
-    EXPECT_TRUE(event->write(state));
-    event->init();
-    return event;
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields) {
+    FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions);
+
+    for (const int field : fields) {
+        dimensions.add_child()->set_field(field);
+    }
+    return dimensions;
 }
 
-std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(
-        android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
-    EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON));
-    event->init();
-    return event;
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
 }
 
-std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(
-        android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
-    EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF));
-    event->init();
-    return event;
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
 }
 
-std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
-    int level, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs);
-    EXPECT_TRUE(event->write(level));
-    event->init();
-    return event;
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+    int pos4[] = {3, 0, 0};
 
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+    Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+    Value value4(tag);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+    key->addValue(FieldValue(field4, value4));
 }
 
-std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& jobName,
-        const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(jobName);
-    event->write(state);
-    event->init();
-    return event;
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+}
+// END: get primary key functions
+
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
+
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
 }
 
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs) {
-    return CreateScheduledJobStateChangedEvent(
-            attributions, name, ScheduledJobStateChanged::STARTED, timestampNs);
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    logEvent->parseBuffer(buf, size);
+
+    AStatsEvent_release(statsEvent);
 }
 
-// Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs) {
-    return CreateScheduledJobStateChangedEvent(
-            attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs);
+void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
+                            int32_t value2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    AStatsEvent_writeInt32(statsEvent, value1);
+    AStatsEvent_writeInt32(statsEvent, value2);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        const WakelockStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
-    event->write(wakelockName);
-    event->write(state);
-    event->init();
-    return event;
-}
-
-std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs) {
-    return CreateWakelockStateChangedEvent(
-        attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs) {
-    return CreateWakelockStateChangedEvent(
-        attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
-    const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(
-        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs);
-    event->write(uid);
-    event->write("pkg_name");
-    event->write("class_name");
-    event->write(state);
-    event->init();
-    return event;
-}
-
-std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
-    return CreateActivityForegroundStateChangedEvent(
-        uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
-    return CreateActivityForegroundStateChangedEvent(
-        uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        const SyncStateChanged::State state, uint64_t timestampNs) {
-    auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
-    event->write(attributions);
-    event->write(name);
-    event->write(state);
-    event->init();
-    return event;
-}
-
-std::unique_ptr<LogEvent> CreateSyncStartEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs) {
-    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateSyncEndEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs) {
-    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
-    const int uid, const ProcessLifeCycleStateChanged::State state, uint64_t timestampNs) {
-    auto logEvent = std::make_unique<LogEvent>(
-        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs);
-    logEvent->write(uid);
-    logEvent->write("");
-    logEvent->write(state);
-    logEvent->init();
+shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                            int32_t value2) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2);
     return logEvent;
 }
 
-std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) {
-    return CreateProcessLifeCycleStateChangedEvent(
-        uid, ProcessLifeCycleStateChanged::CRASHED, timestampNs);
+void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
+                              int32_t value2, int32_t value3) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    AStatsEvent_writeInt32(statsEvent, value1);
+    AStatsEvent_writeInt32(statsEvent, value2);
+    AStatsEvent_writeInt32(statsEvent, value3);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
-    int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs) {
-    auto logEvent = std::make_unique<LogEvent>(
-        android::util::ISOLATED_UID_CHANGED, timestampNs);
-    logEvent->write(hostUid);
-    logEvent->write(isolatedUid);
-    logEvent->write(is_create);
-    logEvent->init();
+shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                              int32_t value2, int32_t value3) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3);
+    return logEvent;
+}
+
+void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs,
+                                 int32_t value) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    AStatsEvent_writeInt32(statsEvent, value);
+    AStatsEvent_writeInt32(statsEvent, value);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value);
+    return logEvent;
+}
+
+void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs);
+    return logEvent;
+}
+
+shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                                     int data2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt32(statsEvent, data1);
+    AStatsEvent_writeInt32(statsEvent, data2);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids, const vector<string>& tags,
+                                             int data1, int data2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+
+    writeAttribution(statsEvent, uids, tags);
+    AStatsEvent_writeInt32(statsEvent, data1);
+    AStatsEvent_writeInt32(statsEvent, data2);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+sp<MockUidMap> makeMockUidMapForOneHost(int hostUid, const vector<int>& isolatedUids) {
+    sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+    EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(ReturnArg<0>());
+    for (const int isolatedUid : isolatedUids) {
+        EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)).WillRepeatedly(Return(hostUid));
+    }
+
+    return uidMap;
+}
+
+sp<MockUidMap> makeMockUidMapForPackage(const string& pkg, const set<int32_t>& uids) {
+    sp<MockUidMap> uidMap = new StrictMock<MockUidMap>();
+    EXPECT_CALL(*uidMap, getAppUid(_)).Times(AnyNumber());
+    EXPECT_CALL(*uidMap, getAppUid(pkg)).WillRepeatedly(Return(uids));
+
+    return uidMap;
+}
+
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(uint64_t timestampNs,
+                                                        const android::view::DisplayStateEnum state,
+                                                        int loggerUid) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(loggerUid, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeInt32(statsEvent, level);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
+        const vector<int>& attributionUids, const vector<string>& attributionTags,
+        const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, jobName.c_str());
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
+                                                       const vector<int>& attributionUids,
+                                                       const vector<string>& attributionTags,
+                                                       const string& jobName) {
+    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
+                                               ScheduledJobStateChanged::STARTED, timestampNs);
+}
+
+// Create log event when scheduled job finishes.
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
+                                                        const vector<int>& attributionUids,
+                                                        const vector<string>& attributionTags,
+                                                        const string& jobName) {
+    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
+                                               ScheduledJobStateChanged::FINISHED, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs,
+                                                          const vector<int>& attributionUids,
+                                                          const vector<string>& attributionTags,
+                                                          const string& wakelockName,
+                                                          const WakelockStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeString(statsEvent, wakelockName.c_str());
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(uint64_t timestampNs,
+                                                     const vector<int>& attributionUids,
+                                                     const vector<string>& attributionTags,
+                                                     const string& wakelockName) {
+    return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags,
+                                           wakelockName, WakelockStateChanged::ACQUIRE);
+}
+
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(uint64_t timestampNs,
+                                                     const vector<int>& attributionUids,
+                                                     const vector<string>& attributionTags,
+                                                     const string& wakelockName) {
+    return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags,
+                                           wakelockName, WakelockStateChanged::RELEASE);
+}
+
+std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
+        uint64_t timestampNs, const int uid, const ActivityForegroundStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, "pkg_name");
+    AStatsEvent_writeString(statsEvent, "class_name");
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid) {
+    return CreateActivityForegroundStateChangedEvent(timestampNs, uid,
+                                                     ActivityForegroundStateChanged::BACKGROUND);
+}
+
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid) {
+    return CreateActivityForegroundStateChangedEvent(timestampNs, uid,
+                                                     ActivityForegroundStateChanged::FOREGROUND);
+}
+
+std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs,
+                                                      const vector<int>& attributionUids,
+                                                      const vector<string>& attributionTags,
+                                                      const string& name,
+                                                      const SyncStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeString(statsEvent, name.c_str());
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
+                                               const vector<int>& attributionUids,
+                                               const vector<string>& attributionTags,
+                                               const string& name) {
+    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
+                                       SyncStateChanged::ON);
+}
+
+std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
+                                             const vector<int>& attributionUids,
+                                             const vector<string>& attributionTags,
+                                             const string& name) {
+    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
+                                       SyncStateChanged::OFF);
+}
+
+std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
+        uint64_t timestampNs, const int uid, const ProcessLifeCycleStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, "");
+    AStatsEvent_writeInt32(statsEvent, state);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, const int uid) {
+    return CreateProcessLifeCycleStateChangedEvent(timestampNs, uid,
+                                                   ProcessLifeCycleStateChanged::CRASHED);
+}
+
+std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::APP_CRASH_OCCURRED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_writeString(statsEvent, "eventType");
+    AStatsEvent_writeString(statsEvent, "processName");
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid,
+                                                        int isolatedUid, bool is_create) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::ISOLATED_UID_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, hostUid);
+    AStatsEvent_writeInt32(statsEvent, isolatedUid);
+    AStatsEvent_writeInt32(statsEvent, is_create);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent(
+        uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::UID_PROCESS_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs,
+                                                         const vector<int>& attributionUids,
+                                                         const vector<string>& attributionTags,
+                                                         const BleScanStateChanged::State state,
+                                                         const bool filtered, const bool firstMatch,
+                                                         const bool opportunistic) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true);
+    if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) {
+        AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET,
+                                       util::BLE_SCAN_STATE_CHANGED__STATE__OFF);
+    }
+    AStatsEvent_writeBool(statsEvent, filtered);  // filtered
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeBool(statsEvent, firstMatch);  // first match
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeBool(statsEvent, opportunistic);  // opportunistic
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid,
+                                                         const string& packageName,
+                                                         const bool usingAlertWindow,
+                                                         const OverlayStateChanged::State state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeString(statsEvent, packageName.c_str());
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_writeBool(statsEvent, usingAlertWindow);
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
-                                              const StatsdConfig& config, const ConfigKey& key) {
-    sp<UidMap> uidMap = new UidMap();
+                                              const StatsdConfig& config, const ConfigKey& key,
+                                              const shared_ptr<IPullAtomCallback>& puller,
+                                              const int32_t atomTag, const sp<UidMap> uidMap) {
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    if (puller != nullptr) {
+        pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {},
+                                                puller);
+    }
     sp<AlarmMonitor> anomalyAlarmMonitor =
-        new AlarmMonitor(1,  [](const sp<IStatsCompanionService>&, int64_t){},
-                [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(1,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     sp<AlarmMonitor> periodicAlarmMonitor =
-        new AlarmMonitor(1,  [](const sp<IStatsCompanionService>&, int64_t){},
-                [](const sp<IStatsCompanionService>&){});
+        new AlarmMonitor(1,
+                         [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+                         [](const shared_ptr<IStatsCompanionService>&){});
     sp<StatsLogProcessor> processor =
             new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
                                   timeBaseNs, [](const ConfigKey&) { return true; },
@@ -467,13 +985,6 @@
     return processor;
 }
 
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
-    AttributionNodeInternal attribution;
-    attribution.set_uid(uid);
-    attribution.set_tag(tag);
-    return attribution;
-}
-
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
   std::sort(events->begin(), events->end(),
             [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
@@ -485,22 +996,25 @@
     return static_cast<int64_t>(std::hash<std::string>()(str));
 }
 
-void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag) {
     EXPECT_EQ(value.field(), atomId);
+    ASSERT_EQ(value.value_tuple().dimensions_value_size(), 2);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
-    // Uid only.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).value_int(), uid);
+    // Uid field.
+    ASSERT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              uid);
+    // Tag field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3);
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag);
 }
 
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
+void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
+    ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
     // Uid only.
@@ -514,7 +1028,7 @@
 
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
+    ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1);
     EXPECT_EQ(value.value_tuple().dimensions_value(node_idx)
@@ -526,7 +1040,7 @@
 void ValidateAttributionUidAndTagDimension(
     const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
+    ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx);
     // Attribution field.
     EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field());
     // Uid only.
@@ -545,7 +1059,7 @@
 void ValidateAttributionUidAndTagDimension(
     const DimensionsValue& value, int atomId, int uid, const std::string& tag) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_EQ(1, value.value_tuple().dimensions_value_size());
+    ASSERT_EQ(1, value.value_tuple().dimensions_value_size());
     // Attribution field.
     EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field());
     // Uid only.
@@ -597,6 +1111,27 @@
     }
 }
 
+bool LessThan(const google::protobuf::RepeatedPtrField<StateValue>& s1,
+              const google::protobuf::RepeatedPtrField<StateValue>& s2) {
+    if (s1.size() != s2.size()) {
+        return s1.size() < s2.size();
+    }
+    for (int i = 0; i < s1.size(); i++) {
+        const StateValue& state1 = s1[i];
+        const StateValue& state2 = s2[i];
+        if (state1.atom_id() != state2.atom_id()) {
+            return state1.atom_id() < state2.atom_id();
+        }
+        if (state1.value() != state2.value()) {
+            return state1.value() < state2.value();
+        }
+        if (state1.group_id() != state2.group_id()) {
+            return state1.group_id() < state2.group_id();
+        }
+    }
+    return false;
+}
+
 bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) {
     if (s1.field() != s2.field()) {
         return s1.field() < s2.field();
@@ -645,7 +1180,7 @@
         return false;
     }
 
-    return LessThan(s1.dimInCondition, s2.dimInCondition);
+    return LessThan(s1.stateValues, s2.stateValues);
 }
 
 void backfillStringInDimension(const std::map<uint64_t, string>& str_map,
@@ -825,6 +1360,34 @@
     }
 }
 
+Status FakeSubsystemSleepCallback::onPullAtom(int atomTag,
+        const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
+    // Convert stats_events into StatsEventParcels.
+    std::vector<StatsEventParcel> parcels;
+    for (int i = 1; i < 3; i++) {
+        AStatsEvent* event = AStatsEvent_obtain();
+        AStatsEvent_setAtomId(event, atomTag);
+        std::string subsystemName = "subsystem_name_";
+        subsystemName = subsystemName + std::to_string(i);
+        AStatsEvent_writeString(event, subsystemName.c_str());
+        AStatsEvent_writeString(event, "subsystem_subname foo");
+        AStatsEvent_writeInt64(event, /*count= */ i);
+        AStatsEvent_writeInt64(event, /*time_millis= */ i * 100);
+        AStatsEvent_build(event);
+        size_t size;
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
+
+        StatsEventParcel p;
+        // vector.assign() creates a copy, but this is inevitable unless
+        // stats_event.h/c uses a vector as opposed to a buffer.
+        p.buffer.assign(buffer, buffer + size);
+        parcels.push_back(std::move(p));
+        AStatsEvent_release(event);
+    }
+    resultReceiver->pullFinished(atomTag, /*success=*/true, parcels);
+    return Status::ok();
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 635c583..3dcf4ec 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -14,20 +14,47 @@
 
 #pragma once
 
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
+
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "src/StatsLogProcessor.h"
-#include "src/logd/LogEvent.h"
 #include "src/hash.h"
+#include "src/logd/LogEvent.h"
+#include "src/packages/UidMap.h"
 #include "src/stats_log_util.h"
-#include "statslog.h"
+#include "stats_event.h"
+#include "statslog_statsdtest.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+using namespace testing;
+using ::aidl::android::os::BnPullAtomCallback;
+using ::aidl::android::os::IPullAtomCallback;
+using ::aidl::android::os::IPullAtomResultReceiver;
+using android::util::ProtoReader;
 using google::protobuf::RepeatedPtrField;
+using Status = ::ndk::ScopedAStatus;
+
+const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED;
+const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED;
+
+enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE };
+
+class MockUidMap : public UidMap {
+public:
+    MOCK_METHOD(int, getHostUidOrSelf, (int uid), (const));
+    MOCK_METHOD(std::set<int32_t>, getAppUid, (const string& package), (const));
+};
+
+// Converts a ProtoOutputStream to a StatsLogReport proto.
+StatsLogReport outputStreamToProto(ProtoOutputStream* proto);
 
 // Create AtomMatcher proto to simply match a specific atom type.
 AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
@@ -53,6 +80,12 @@
 // Create AtomMatcher proto for stopping battery save mode.
 AtomMatcher CreateBatterySaverModeStopAtomMatcher();
 
+// Create AtomMatcher proto for battery state none mode.
+AtomMatcher CreateBatteryStateNoneMatcher();
+
+// Create AtomMatcher proto for battery state usb mode.
+AtomMatcher CreateBatteryStateUsbMatcher();
+
 // Create AtomMatcher proto for process state changed.
 AtomMatcher CreateUidProcessStateChangedAtomMatcher();
 
@@ -95,6 +128,9 @@
 // Create Predicate proto for battery saver mode.
 Predicate CreateBatterySaverModePredicate();
 
+// Create Predicate proto for device unplogged mode.
+Predicate CreateDeviceUnpluggedPredicate();
+
 // Create Predicate proto for holding wakelock.
 Predicate CreateHoldingWakelockPredicate();
 
@@ -104,6 +140,39 @@
 // Create a Predicate proto for app is in background.
 Predicate CreateIsInBackgroundPredicate();
 
+// Create State proto for screen state atom.
+State CreateScreenState();
+
+// Create State proto for uid process state atom.
+State CreateUidProcessState();
+
+// Create State proto for overlay state atom.
+State CreateOverlayState();
+
+// Create State proto for screen state atom with on/off map.
+State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
+// Create State proto for screen state atom with simple on/off map.
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
+// Create StateGroup proto for ScreenState ON group
+StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId);
+
+// Create StateGroup proto for ScreenState OFF group
+StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId);
+
+// Create StateGroup proto for simple ScreenState ON group
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId);
+
+// Create StateGroup proto for simple ScreenState OFF group
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId);
+
+// Create StateMap proto for ScreenState ON/OFF map
+StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
+// Create StateMap proto for simple ScreenState ON/OFF map
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
 // Add a predicate to the predicate combination.
 void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
 
@@ -118,76 +187,156 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields);
+
+// START: get primary key functions
+// These functions take in atom field information and create FieldValues which are stored in the
+// given HashableDimensionKey.
+void getUidProcessKey(int uid, HashableDimensionKey* key);
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key);
+// END: get primary key functions
+
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent.
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
+shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                            int32_t value2);
+
+void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
+                            int32_t value2);
+
+shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                              int32_t value2, int32_t value3);
+
+void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
+                              int32_t value2, int32_t value3);
+
+// The repeated value log event helpers create a log event with two int fields, both
+// set to the same value. This is useful for testing metrics that are only interested
+// in the value of the second field but still need the first field to be populated.
+std::shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs,
+                                                      int32_t value);
+
+void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs,
+                                 int32_t value);
+
+std::shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs);
+
+void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs);
+
+std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                                          int data2);
+
+std::shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs,
+                                                  const vector<int>& uids,
+                                                  const vector<string>& tags, int data1, int data2);
+
+sp<MockUidMap> makeMockUidMapForOneHost(int hostUid, const vector<int>& isolatedUids);
+
+sp<MockUidMap> makeMockUidMapForPackage(const string& pkg, const set<int32_t>& uids);
+
 // Create log event for screen state changed.
-std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-    const android::view::DisplayStateEnum state, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(uint64_t timestampNs,
+                                                        const android::view::DisplayStateEnum state,
+                                                        int loggerUid = 0);
 
 // Create log event for screen brightness state changed.
-std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
-   int level, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level);
 
 // Create log event when scheduled job starts.
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
+                                                       const vector<int>& attributionUids,
+                                                       const vector<string>& attributionTags,
+                                                       const string& jobName);
 
 // Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
-    const std::vector<AttributionNodeInternal>& attributions,
-    const string& name, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
+                                                        const vector<int>& attributionUids,
+                                                        const vector<string>& attributionTags,
+                                                        const string& jobName);
 
 // Create log event when battery saver starts.
 std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs);
 // Create log event when battery saver stops.
 std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
 
+// Create log event when battery state changes.
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+
 // Create log event for app moving to background.
-std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
 
 // Create log event for app moving to foreground.
-std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid);
 
 // Create log event when the app sync starts.
-std::unique_ptr<LogEvent> CreateSyncStartEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, const vector<int>& uids,
+                                               const vector<string>& tags, const string& name);
 
 // Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateSyncEndEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& name,
-        uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, const vector<int>& uids,
+                                             const vector<string>& tags, const string& name);
 
 // Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateAppCrashEvent(
-    const int uid, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, const int uid);
+
+// Create log event for an app crash.
+std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid);
 
 // Create log event for acquiring wakelock.
-std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(uint64_t timestampNs, const vector<int>& uids,
+                                                     const vector<string>& tags,
+                                                     const string& wakelockName);
 
 // Create log event for releasing wakelock.
-std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
-        const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
-        uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(uint64_t timestampNs, const vector<int>& uids,
+                                                     const vector<string>& tags,
+                                                     const string& wakelockName);
 
 // Create log event for releasing wakelock.
-std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
-    int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs);
+std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid,
+                                                        int isolatedUid, bool is_create);
 
-// Helper function to create an AttributionNodeInternal proto.
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
+// Create log event for uid process state change.
+std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent(
+        uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state);
+
+std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs,
+                                                         const vector<int>& attributionUids,
+                                                         const vector<string>& attributionTags,
+                                                         const BleScanStateChanged::State state,
+                                                         const bool filtered, const bool firstMatch,
+                                                         const bool opportunistic);
+
+std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid,
+                                                         const string& packageName,
+                                                         const bool usingAlertWindow,
+                                                         const OverlayStateChanged::State state);
 
 // Create a statsd log event processor upon the start time in seconds, config and key.
-sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs,
-                                              const int64_t currentTimeNs,
-                                              const StatsdConfig& config, const ConfigKey& key);
+sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
+                                              const StatsdConfig& config, const ConfigKey& key,
+                                              const shared_ptr<IPullAtomCallback>& puller = nullptr,
+                                              const int32_t atomTag = 0 /*for puller only*/,
+                                              const sp<UidMap> = new UidMap());
 
 // Util function to sort the log events by timestamp.
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
 
 int64_t StringToId(const string& str);
 
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
 void ValidateAttributionUidAndTagDimension(
@@ -196,12 +345,14 @@
     const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag);
 
 struct DimensionsPair {
-    DimensionsPair(DimensionsValue m1, DimensionsValue m2) : dimInWhat(m1), dimInCondition(m2){};
+    DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField<StateValue> m2)
+        : dimInWhat(m1), stateValues(m2){};
 
     DimensionsValue dimInWhat;
-    DimensionsValue dimInCondition;
+    google::protobuf::RepeatedPtrField<StateValue> stateValues;
 };
 
+bool LessThan(const StateValue& s1, const StateValue& s2);
 bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2);
 bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2);
 
@@ -233,6 +384,12 @@
                            const google::protobuf::RepeatedPtrField<DimensionsValue>& leafValues,
                            DimensionsValue* dimension);
 
+class FakeSubsystemSleepCallback : public BnPullAtomCallback {
+public:
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override;
+};
+
 template <typename T>
 void backfillDimensionPath(const DimensionsValue& whatPath,
                            const DimensionsValue& conditionPath,
@@ -264,7 +421,7 @@
     for (int i = 0; i < metricData.data_size(); ++i) {
         dimensionIndexMap.insert(
                 std::make_pair(DimensionsPair(metricData.data(i).dimensions_in_what(),
-                                              metricData.data(i).dimensions_in_condition()),
+                                              metricData.data(i).slice_by_state()),
                                i));
     }
     for (const auto& itr : dimensionIndexMap) {
@@ -319,4 +476,4 @@
 }
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp
index 9e15e99..74eafbf 100644
--- a/cmds/statsd/tests/storage/StorageManager_test.cpp
+++ b/cmds/statsd/tests/storage/StorageManager_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android-base/unique_fd.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -39,47 +40,19 @@
 
     bool result;
 
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
+    result = StorageManager::writeTrainInfo(trainInfo);
 
     EXPECT_TRUE(result);
 
     InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
+    result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult);
     EXPECT_TRUE(result);
 
     EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
-    EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
+    ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
     EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName);
     EXPECT_EQ(trainInfo.status, trainInfoResult.status);
-    EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
-    EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
-}
-
-TEST(StorageManagerTest, TrainInfoReadWriteEmptyTrainNameTest) {
-    InstallTrainInfo trainInfo;
-    trainInfo.trainVersionCode = 12345;
-    trainInfo.trainName = "";
-    trainInfo.status = 1;
-    const char* expIds = "test_ids";
-    trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds));
-
-    bool result;
-
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
-
-    EXPECT_TRUE(result);
-
-    InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
-    EXPECT_TRUE(result);
-
-    EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
-    EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
-    EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName);
-    EXPECT_EQ(trainInfo.status, trainInfoResult.status);
-    EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
+    ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
     EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
 }
 
@@ -93,20 +66,19 @@
 
     bool result;
 
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
+    result = StorageManager::writeTrainInfo(trainInfo);
 
     EXPECT_TRUE(result);
 
     InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
+    result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult);
     EXPECT_TRUE(result);
 
     EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
-    EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
+    ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
     EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName);
     EXPECT_EQ(trainInfo.status, trainInfoResult.status);
-    EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
+    ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
     EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
 }
 
diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
new file mode 100644
index 0000000..32cecd3
--- /dev/null
+++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 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 "utils/MultiConditionTrigger.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <set>
+#include <thread>
+#include <vector>
+
+#ifdef __ANDROID__
+
+using namespace std;
+using std::this_thread::sleep_for;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MultiConditionTrigger, TestMultipleConditions) {
+    int numConditions = 5;
+    string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
+    set<string> conditionNames = {t1, t2, t3, t4, t5};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    // Mark done as true and notify in the done.
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    vector<thread> threads;
+    vector<int> done(numConditions, 0);
+
+    int i = 0;
+    for (const string& conditionName : conditionNames) {
+        threads.emplace_back([&done, &conditionName, &trigger, i] {
+            sleep_for(chrono::milliseconds(3));
+            done[i] = 1;
+            trigger.markComplete(conditionName);
+        });
+        i++;
+    }
+
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] {
+        return triggerCalled;
+    });
+
+    for (i = 0; i < numConditions; i++) {
+        EXPECT_EQ(done[i], 1);
+    }
+
+    for (i = 0; i < numConditions; i++) {
+        threads[i].join();
+    }
+}
+
+TEST(MultiConditionTrigger, TestNoConditions) {
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+    EXPECT_TRUE(triggerCalled);
+    // Ensure that trigger occurs immediately if no events need to be completed.
+}
+
+TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) {
+    string t1 = "t1", t2 = "t2";
+    set<string> conditionNames = {t1, t2};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    trigger.markComplete(t1);
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger still hasn't fired.
+    {
+        lock_guard lg(lock);
+        EXPECT_FALSE(triggerCalled);
+    }
+
+    trigger.markComplete(t2);
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+    EXPECT_TRUE(triggerCalled);
+}
+
+TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) {
+    string t1 = "t1";
+    set<string> conditionNames = {t1};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+    int triggerCount = 0;
+
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] {
+        {
+            lock_guard lg(lock);
+            triggerCount++;
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger fired.
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+        EXPECT_TRUE(triggerCalled);
+        EXPECT_EQ(triggerCount, 1);
+        triggerCalled = false;
+    }
+
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger does not fire again.
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; });
+        EXPECT_FALSE(triggerCalled);
+        EXPECT_EQ(triggerCount, 1);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tools/dogfood/Android.bp b/cmds/statsd/tools/dogfood/Android.bp
deleted file mode 100644
index bb494a6..0000000
--- a/cmds/statsd/tools/dogfood/Android.bp
+++ /dev/null
@@ -1,37 +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.
-//
-//
-
-android_app {
-    name: "StatsdDogfood",
-    platform_apis: true,
-
-    srcs: ["src/**/*.java"],
-
-    resource_dirs: ["res"],
-    static_libs: [
-        "platformprotoslite",
-        "statsdprotolite",
-    ],
-
-    privileged: true,
-    dex_preopt: {
-        enabled: false,
-    },
-    certificate: "platform",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
deleted file mode 100644
index 52673fb..0000000
--- a/cmds/statsd/tools/dogfood/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.statsd.dogfood"
-    android:sharedUserId="android.uid.system"
-    android:versionCode="1"
-    android:versionName="1.0" >
-
-    <uses-permission android:name="android.permission.DUMP" />
-
-    <application
-        android:allowBackup="true"
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name"
-            android:launchMode="singleTop" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-
-        <service android:name=".MainActivity$ReceiverIntentService" android:exported="true" />
-    </application>
-</manifest>
diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 55621cc..0000000
--- a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index 11ec206..0000000
--- a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index 7c02b78..0000000
--- a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 915d914..0000000
--- a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
deleted file mode 100644
index 784ed40..0000000
--- a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <Button
-            android:id="@+id/push_config"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:background="@android:color/holo_green_light"
-            android:text="@string/push_config"/>
-        <Button
-                android:id="@+id/set_receiver"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:background="@android:color/holo_green_light"
-                android:text="@string/set_receiver"/>
-        <Button
-                android:id="@+id/remove_receiver"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:background="@android:color/holo_green_light"
-                android:text="@string/remove_receiver"/>
-
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <Button android:id="@+id/app_a_wake_lock_acquire1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_a_get_wl1"/>
-            <Button android:id="@+id/app_a_wake_lock_release1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_a_release_wl1"/>
-        </LinearLayout>
-
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <Button android:id="@+id/app_a_wake_lock_acquire2"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_a_get_wl2"/>
-            <Button android:id="@+id/app_a_wake_lock_release2"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_a_release_wl2"/>
-        </LinearLayout>
-
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <Button android:id="@+id/app_b_wake_lock_acquire1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_b_get_wl1"/>
-            <Button android:id="@+id/app_b_wake_lock_release1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_b_release_wl1"/>
-        </LinearLayout>
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <Button android:id="@+id/app_b_wake_lock_acquire2"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_b_get_wl2"/>
-            <Button android:id="@+id/app_b_wake_lock_release2"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_b_release_wl2"/>
-        </LinearLayout>
-
-    <LinearLayout android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <Button android:id="@+id/plug"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/plug"/>
-
-        <Button android:id="@+id/unplug"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/unplug"/>
-    </LinearLayout>
-
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <Button android:id="@+id/screen_on"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/screen_on"/>
-
-            <Button android:id="@+id/screen_off"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/screen_off"/>
-        </LinearLayout>
-
-        <LinearLayout android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:id="@+id/custom_start"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/custom_start" />
-
-            <Button
-                android:id="@+id/custom_stop"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/custom_stop" />
-        </LinearLayout>
-
-        <Button android:id="@+id/dump"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:background="@android:color/holo_purple"
-            android:text="@string/dump"/>
-
-        <TextView
-            android:id="@+id/header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/report_header"/>
-
-        <TextView
-            android:id="@+id/report_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-</ScrollView>
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
deleted file mode 100644
index d050061..0000000
--- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
deleted file mode 100644
index 60948a1..0000000
--- a/cmds/statsd/tools/dogfood/res/values/strings.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<resources>
-
-    <string name="app_name">Statsd Dogfood</string>
-
-    <string name="statsd_running">Statsd Running</string>
-    <string name="statsd_not_running">Statsd NOT Running</string>
-
-    <string name="push_config">Push baseline config</string>
-    <string name="set_receiver">Set pendingintent</string>
-    <string name="remove_receiver">Remove pendingintent</string>
-
-    <string name="app_a_foreground">App A foreground</string>
-    <string name="app_b_foreground">App B foreground</string>
-
-
-    <string name="app_a_get_wl1">App A get wl_1</string>
-    <string name="app_a_release_wl1">App A release wl_1</string>
-
-    <string name="app_a_get_wl2">App A get wl_2</string>
-    <string name="app_a_release_wl2">App A release wl_2</string>
-
-    <string name="app_b_get_wl1">App B get wl_1</string>
-    <string name="app_b_release_wl1">App B release wl_1</string>
-
-    <string name="app_b_get_wl2">App B get wl_2</string>
-    <string name="app_b_release_wl2">App B release wl_2</string>
-
-    <string name="plug">Plug</string>
-    <string name="unplug">Unplug</string>
-
-    <string name="screen_on">Screen On</string>
-    <string name="screen_off">Screen Off</string>
-
-    <string name="custom_start">App hook start</string>
-    <string name="custom_stop">App hook stop</string>
-
-    <string name="dump">DumpReport</string>
-    <string name="report_header">Report details</string>
-</resources>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
deleted file mode 100644
index b6b16e4..0000000
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
+++ /dev/null
@@ -1,158 +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.
- */
-package com.android.statsd.dogfood;
-
-import android.text.format.DateFormat;
-
-import com.android.os.StatsLog;
-
-import java.util.List;
-
-public class DisplayProtoUtils {
-    public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReportList reports) {
-        sb.append("ConfigKey: ");
-        if (reports.hasConfigKey()) {
-            com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
-            sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId())
-                    .append("\n");
-        }
-
-        for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) {
-            sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
-            sb.append("Last report time:").append(getDateStr(report.getLastReportElapsedNanos())).
-                    append("\n");
-            sb.append("Current report time:").append(getDateStr(report.getCurrentReportElapsedNanos())).
-                    append("\n");
-            for (StatsLog.StatsLogReport log : report.getMetricsList()) {
-                sb.append("\n\n");
-                sb.append("metric id: ").append(log.getMetricId()).append("\n");
-
-                switch (log.getDataCase()) {
-                    case DURATION_METRICS:
-                        sb.append("Duration metric data\n");
-                        displayDurationMetricData(sb, log);
-                        break;
-                    case EVENT_METRICS:
-                        sb.append("Event metric data\n");
-                        displayEventMetricData(sb, log);
-                        break;
-                    case COUNT_METRICS:
-                        sb.append("Count metric data\n");
-                        displayCountMetricData(sb, log);
-                        break;
-                    case GAUGE_METRICS:
-                        sb.append("Gauge metric data\n");
-                        displayGaugeMetricData(sb, log);
-                        break;
-                    case VALUE_METRICS:
-                        sb.append("Value metric data\n");
-                        displayValueMetricData(sb, log);
-                        break;
-                    case DATA_NOT_SET:
-                        sb.append("No metric data\n");
-                        break;
-                }
-            }
-        }
-    }
-
-    public static String getDateStr(long nanoSec) {
-        return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
-    }
-
-    private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
-        sb.append(dimensionValue.getField()).append(":");
-        if (dimensionValue.hasValueBool()) {
-            sb.append(dimensionValue.getValueBool());
-        } else if (dimensionValue.hasValueFloat()) {
-            sb.append(dimensionValue.getValueFloat());
-        } else if (dimensionValue.hasValueInt()) {
-            sb.append(dimensionValue.getValueInt());
-        } else if (dimensionValue.hasValueStr()) {
-            sb.append(dimensionValue.getValueStr());
-        } else if (dimensionValue.hasValueTuple()) {
-            sb.append("{");
-            for (StatsLog.DimensionsValue child :
-                    dimensionValue.getValueTuple().getDimensionsValueList()) {
-                displayDimension(sb, child);
-            }
-            sb.append("}");
-        }
-        sb.append(" ");
-    }
-
-    public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper
-                = log.getDurationMetrics();
-        sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
-        for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
-            sb.append("dimension_in_what: ");
-            displayDimension(sb, duration.getDimensionsInWhat());
-            sb.append("\n");
-            if (duration.hasDimensionsInCondition()) {
-                sb.append("dimension_in_condition: ");
-                displayDimension(sb, duration.getDimensionsInCondition());
-                sb.append("\n");
-            }
-
-            for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList())  {
-                sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-")
-                        .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ")
-                        .append(info.getDurationNanos()).append(" ns\n");
-            }
-        }
-    }
-
-    public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n");
-        StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper =
-                log.getEventMetrics();
-        for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) {
-            sb.append(getDateStr(event.getElapsedTimestampNanos())).append(": ");
-            sb.append(event.getAtom().getPushedCase().toString()).append("\n");
-        }
-    }
-
-    public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper
-                = log.getCountMetrics();
-        sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
-        for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
-            sb.append("dimension_in_what: ");
-            displayDimension(sb, count.getDimensionsInWhat());
-            sb.append("\n");
-            if (count.hasDimensionsInCondition()) {
-                sb.append("dimension_in_condition: ");
-                displayDimension(sb, count.getDimensionsInCondition());
-                sb.append("\n");
-            }
-
-            for (StatsLog.CountBucketInfo info : count.getBucketInfoList())  {
-                sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-")
-                        .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ")
-                        .append(info.getCount()).append("\n");
-            }
-        }
-    }
-
-    public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Display me!");
-    }
-
-    public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Display me!");
-    }
-}
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
deleted file mode 100644
index 4f4dd01..0000000
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ /dev/null
@@ -1,361 +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.
- */
-package com.android.statsd.dogfood;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.IntentService;
-import android.app.StatsManager;
-import android.app.StatsManager.StatsUnavailableException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.util.Log;
-import android.util.StatsLog;
-import android.view.View;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.os.IStatsManager;
-import android.os.ServiceManager;
-
-import java.io.InputStream;
-
-import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport;
-
-public class MainActivity extends Activity {
-    private final static String TAG = "StatsdDogfood";
-    private final static long CONFIG_ID = 987654321;
-
-    final int[] mUids = {11111111, 2222222};
-    StatsManager mStatsManager;
-    TextView mReportText;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.activity_main);
-
-        findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockAcquire(0, "wl_1");
-                    }
-                });
-
-        findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockAcquire(1, "wl_1");
-                    }
-                });
-
-        findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockAcquire(0, "wl_2");
-                    }
-                });
-
-        findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockAcquire(1, "wl_2");
-                    }
-                });
-
-        findViewById(R.id.app_a_wake_lock_release1).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockRelease(0, "wl_1");
-                    }
-                });
-
-
-        findViewById(R.id.app_b_wake_lock_release1).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockRelease(1, "wl_1");
-                    }
-                });
-
-        findViewById(R.id.app_a_wake_lock_release2).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockRelease(0, "wl_2");
-                    }
-                });
-
-
-        findViewById(R.id.app_b_wake_lock_release2).setOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        onWakeLockRelease(1, "wl_2");
-                    }
-                });
-
-
-        findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED,
-                        StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_AC);
-            }
-        });
-
-        findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED,
-                        StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_NONE);
-            }
-        });
-
-        findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
-                        StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON);
-            }
-        });
-
-        findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
-                        StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF);
-            }
-        });
-
-        findViewById(R.id.custom_start).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.logStart(8);
-            }
-        });
-
-        findViewById(R.id.custom_stop).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                StatsLog.logStop(8);
-            }
-        });
-
-        mReportText = (TextView) findViewById(R.id.report_text);
-
-        findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                if (!statsdRunning()) {
-                    return;
-                }
-                if (mStatsManager != null) {
-                    try {
-                        byte[] data = mStatsManager.getReports(CONFIG_ID);
-                        if (data != null) {
-                            displayData(data);
-                            return;
-                        }
-                    } catch (StatsUnavailableException e) {
-                        Log.e(TAG, "Failed to get data from statsd", e);
-                    }
-                    mReportText.setText("Failed!");
-                }
-            }
-        });
-
-        findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                try {
-                    if (!statsdRunning()) {
-                        return;
-                    }
-                    Resources res = getResources();
-                    InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config);
-
-                    byte[] config = new byte[inputStream.available()];
-                    inputStream.read(config);
-                    if (mStatsManager != null) {
-                        try {
-                            mStatsManager.addConfig(CONFIG_ID, config);
-                            Toast.makeText(
-                                    MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
-                        } catch (StatsUnavailableException | IllegalArgumentException e) {
-                            Toast.makeText(MainActivity.this, "Config push FAILED!",
-                                    Toast.LENGTH_LONG).show();
-                        }
-                    }
-                } catch (Exception e) {
-                    Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG);
-                }
-            }
-        });
-
-        PendingIntent pi = PendingIntent.getService(this, 0,
-                new Intent(this, ReceiverIntentService.class), PendingIntent.FLAG_UPDATE_CURRENT);
-        findViewById(R.id.set_receiver).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                try {
-                    if (!statsdRunning()) {
-                        return;
-                    }
-                    if (mStatsManager != null) {
-                        try {
-                            mStatsManager.setFetchReportsOperation(pi, CONFIG_ID);
-                            Toast.makeText(MainActivity.this,
-                                    "Receiver specified to pending intent", Toast.LENGTH_LONG)
-                                    .show();
-                        } catch (StatsUnavailableException e) {
-                            Toast.makeText(MainActivity.this, "Statsd did not set receiver",
-                                    Toast.LENGTH_LONG)
-                                    .show();
-                        }
-                    }
-                } catch (Exception e) {
-                    Toast.makeText(MainActivity.this, "failed to set receiver", Toast.LENGTH_LONG);
-                }
-            }
-        });
-        findViewById(R.id.remove_receiver).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                try {
-                    if (!statsdRunning()) {
-                        return;
-                    }
-                    if (mStatsManager != null) {
-                        try {
-                            mStatsManager.setFetchReportsOperation(null, CONFIG_ID);
-                            Toast.makeText(MainActivity.this, "Receiver remove", Toast.LENGTH_LONG)
-                                    .show();
-                        } catch (StatsUnavailableException e) {
-                            Toast.makeText(MainActivity.this, "Statsd did not remove receiver",
-                                    Toast.LENGTH_LONG)
-                                    .show();
-                        }
-                    }
-                } catch (Exception e) {
-                    Toast.makeText(
-                            MainActivity.this, "failed to remove receiver", Toast.LENGTH_LONG);
-                }
-            }
-        });
-        mStatsManager = (StatsManager) getSystemService("stats");
-    }
-
-    private boolean statsdRunning() {
-        if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) {
-            Log.d(TAG, "Statsd not running");
-            Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show();
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0));
-        int pkg = intent.getIntExtra("pkg", 0);
-        String name = intent.getStringExtra("name");
-        if (intent.hasExtra("acquire")) {
-            onWakeLockAcquire(pkg, name);
-        } else if (intent.hasExtra("release")) {
-            onWakeLockRelease(pkg, name);
-        }
-    }
-
-    private void displayData(byte[] data) {
-        com.android.os.StatsLog.ConfigMetricsReportList reports = null;
-        boolean good = false;
-        if (data != null) {
-            try {
-                reports = com.android.os.StatsLog.ConfigMetricsReportList.parseFrom(data);
-                good = true;
-            } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-                // display it in the text view.
-            }
-        }
-        int size = data == null ? 0 : data.length;
-        StringBuilder sb = new StringBuilder();
-        sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!");
-        sb.append(" size:").append(size).append("\n");
-
-        if (good && reports != null) {
-            displayLogReport(sb, reports);
-            mReportText.setText(sb.toString());
-        }
-    }
-
-
-    private void onWakeLockAcquire(int id, String name) {
-        if (id > 1) {
-            Log.d(TAG, "invalid pkg id");
-            return;
-        }
-        int[] uids = new int[]{mUids[id]};
-        String[] tags = new String[]{"acquire"};
-        StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags,
-                StatsLog.WAKELOCK_STATE_CHANGED__TYPE__PARTIAL_WAKE_LOCK, name,
-                StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
-        StringBuilder sb = new StringBuilder();
-        sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
-                .append(", ").append(name).append(", 1);");
-        Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
-    }
-
-    private void onWakeLockRelease(int id, String name) {
-        if (id > 1) {
-            Log.d(TAG, "invalid pkg id");
-            return;
-        }
-        int[] uids = new int[]{mUids[id]};
-        String[] tags = new String[]{"release"};
-        StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags,
-                StatsLog.WAKELOCK_STATE_CHANGED__TYPE__PARTIAL_WAKE_LOCK, name,
-                StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
-        StringBuilder sb = new StringBuilder();
-        sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
-                .append(", ").append(name).append(", 0);");
-        Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
-    }
-
-    public static class ReceiverIntentService extends IntentService {
-        public ReceiverIntentService() {
-            super("ReceiverIntentService");
-        }
-
-        /**
-         * The IntentService calls this method from the default worker thread with
-         * the intent that started the service. When this method returns, IntentService
-         * stops the service, as appropriate.
-         */
-        @Override
-        protected void onHandleIntent(Intent intent) {
-            Log.i(TAG, "Received notification that we should call getData");
-        }
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/Android.bp b/cmds/statsd/tools/loadtest/Android.bp
deleted file mode 100644
index bf87fc5..0000000
--- a/cmds/statsd/tools/loadtest/Android.bp
+++ /dev/null
@@ -1,37 +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.
-//
-//
-
-android_app {
-    name: "StatsdLoadtest",
-    platform_apis: true,
-
-    srcs: ["src/**/*.java"],
-
-    resource_dirs: ["res"],
-    static_libs: [
-        "platformprotoslite",
-        "statsdprotolite",
-    ],
-
-    certificate: "platform",
-    privileged: true,
-    dex_preopt: {
-        enabled: false,
-    },
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/cmds/statsd/tools/loadtest/AndroidManifest.xml b/cmds/statsd/tools/loadtest/AndroidManifest.xml
deleted file mode 100644
index 2bf8ca9..0000000
--- a/cmds/statsd/tools/loadtest/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.statsd.loadtest"
-    android:sharedUserId="android.uid.system"
-    android:versionCode="1"
-    android:versionName="1.0" >
-
-  <uses-permission android:name="android.permission.DUMP" />
-  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-  <application
-      android:allowBackup="true"
-      android:icon="@drawable/ic_launcher"
-      android:label="@string/app_name" >
-    <activity
-        android:name=".LoadtestActivity"
-        android:label="@string/app_name"
-        android:launchMode="singleTop" >
-      <intent-filter>
-        <action android:name="android.intent.action.MAIN" />
-        <category android:name="android.intent.category.LAUNCHER" />
-      </intent-filter>
-    </activity>
-    <receiver android:name=".LoadtestActivity$PusherAlarmReceiver" />
-    <receiver android:name=".LoadtestActivity$StopperAlarmReceiver"/>
-    <receiver android:name=".PerfData$PerfAlarmReceiver"/>
-  </application>
-</manifest>
diff --git a/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 55621cc..0000000
--- a/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index 11ec206..0000000
--- a/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index 7c02b78..0000000
--- a/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 915d914..0000000
--- a/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
deleted file mode 100644
index d6f8047..0000000
--- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
+++ /dev/null
@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout
-        android:id="@+id/outside"
-        android:clickable="true"
-        android:focusable="true"
-        android:focusableInTouchMode="true"
-        android:gravity="center"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginRight="10dp"
-        android:layout_marginLeft="10dp"
-        android:orientation="vertical">
-      <requestFocus />
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal">
-            <TextView
-                android:textSize="30dp"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:text="@string/replication_label" />
-            <EditText
-                android:id="@+id/replication"
-                android:inputType="number"
-                android:layout_weight="1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLength="4"
-                android:text="@integer/replication_default"
-                android:textSize="30dp"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal">
-            <TextView
-                android:textSize="30dp"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:text="@string/bucket_label" />
-             <Spinner
-                 android:id="@+id/bucket_spinner"
-                 android:layout_width="wrap_content"
-                 android:layout_height="wrap_content"
-                 android:prompt="@string/bucket_label"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal">
-            <TextView
-                android:textSize="30dp"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:text="@string/period_label" />
-            <EditText
-                android:id="@+id/period"
-                android:inputType="number"
-                android:layout_weight="1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:maxLength="3"
-                android:text="@integer/period_default"
-                android:textSize="30dp"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal">
-            <TextView
-                android:textSize="30dp"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:text="@string/burst_label" />
-            <EditText
-                android:id="@+id/burst"
-                android:inputType="number"
-                android:layout_weight="1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLength="4"
-                android:text="@integer/burst_default"
-                android:textSize="30dp"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal">
-            <TextView
-                android:textSize="30dp"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:text="@string/duration_label" />
-            <EditText
-                android:id="@+id/duration"
-                android:inputType="number"
-                android:layout_weight="1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:maxLength="4"
-                android:text="@integer/duration_default"
-                android:textSize="30dp"/>
-        </LinearLayout>
-        <CheckBox
-            android:id="@+id/placebo"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/placebo"
-            android:checked="false" />
-
-        <LinearLayout
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-            <CheckBox
-                android:id="@+id/include_count"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/count"
-                android:checked="true"/>
-            <CheckBox
-                android:id="@+id/include_duration"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/duration"
-                android:checked="true"/>
-            <CheckBox
-                android:id="@+id/include_event"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/event"
-                android:checked="true"/>
-            <CheckBox
-                android:id="@+id/include_value"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/value"
-                android:checked="true"/>
-            <CheckBox
-                android:id="@+id/include_gauge"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/gauge"
-                android:checked="true"/>
-        </LinearLayout>
-
-        <Space
-            android:layout_width="1dp"
-            android:layout_height="30dp"/>
-
-        <Button
-            android:id="@+id/start_stop"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:background="#ffff0000"
-            android:text="@string/start"
-            android:textSize="50dp"/>
-
-        <Space
-            android:layout_width="1dp"
-            android:layout_height="30dp"/>
-
-        <Space
-            android:layout_width="1dp"
-            android:layout_height="30dp"/>
-
-        <TextView
-            android:id="@+id/report_text"
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-</ScrollView>
diff --git a/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml b/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml
deleted file mode 100644
index b03da06..0000000
--- a/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:textSize="30dp"
-    android:gravity="left"
-    android:padding="5dip"
-    />
diff --git a/cmds/statsd/tools/loadtest/res/raw/loadtest_config b/cmds/statsd/tools/loadtest/res/raw/loadtest_config
deleted file mode 100755
index 2422190..0000000
--- a/cmds/statsd/tools/loadtest/res/raw/loadtest_config
+++ /dev/null
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/values/integers.xml b/cmds/statsd/tools/loadtest/res/values/integers.xml
deleted file mode 100644
index c2407d3..0000000
--- a/cmds/statsd/tools/loadtest/res/values/integers.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<resources>
-    <integer name="burst_default">1</integer>
-    <integer name="period_default">2</integer>
-    <integer name="replication_default">1</integer>
-    <integer name="duration_default">240</integer>
-</resources>
diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml
deleted file mode 100644
index e8ae3f8..0000000
--- a/cmds/statsd/tools/loadtest/res/values/strings.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-<resources>
-    <string name="app_name">Statsd Loadtest</string>
-    <string name="bucket_label">bucket size (mins):&#160;</string>
-    <string name="burst_label">burst:&#160;</string>
-    <string name="bucket_default">FIVE_MINUTES</string>
-    <string name="placebo">placebo</string>
-    <string name="period_label">logging period (secs):&#160;</string>
-    <string name="replication_label">metric replication:&#160;</string>
-    <string name="duration_label">test duration (mins):&#160;</string>
-    <string name="start"> &#160;Start&#160; </string>
-    <string name="stop"> &#160;Stop&#160; </string>
-    <string name="count"> count </string>
-    <string name="duration"> duration </string>
-    <string name="event"> event </string>
-    <string name="value"> value </string>
-    <string name="gauge"> gauge </string>
-
-</resources>
diff --git a/cmds/statsd/tools/loadtest/run_loadtest.sh b/cmds/statsd/tools/loadtest/run_loadtest.sh
deleted file mode 100755
index 3c93a06..0000000
--- a/cmds/statsd/tools/loadtest/run_loadtest.sh
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/bin/sh
-#
-# Script that measures statsd's PSS under an increasing number of metrics.
-
-# Globals.
-pss=""
-pid=""
-
-# Starts the loadtest.
-start_loadtest() {
-    echo "Starting loadtest"
-    adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "start"
-}
-
-# Stops the loadtest.
-stop_loadtest() {
-    echo "Stopping loadtest"
-    adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "stop"
-}
-
-# Sets the metrics replication.
-# Arguments:
-#   $1: The replication factor.
-set_replication() {
-    adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "set_replication" --ei "replication" "${1}"
-    echo "Replication set to ${1}"
-}
-
-# Reads statsd's pid and PSS.
-update_pid_and_pss() {
-    # Command that reads the PSS for statsd. This also gives us its pid.
-    get_mem=$(adb shell dumpsys meminfo |grep statsd)
-    # Looks for statsd's pid.
-    regex="([0-9,]+)K: statsd \(pid ([0-9]+)\).*"
-    if [[ $get_mem =~ $regex ]]; then
-        pss=$(echo "${BASH_REMATCH[1]}" | tr -d , | sed 's/\.//g')
-        pid=$(echo "${BASH_REMATCH[2]}")
-    else
-        echo $cmd doesnt match $regex
-    fi
-}
-
-# Kills statsd.
-# Assumes the pid has been set.
-kill_statsd() {
-    echo "Killing statsd (pid ${pid})"
-    adb shell kill -9 "${pid}"
-}
-
-# Main loop.
-main() {
-    start_time=$(date +%s)
-    values=()
-    stop_loadtest
-
-    echo ""
-    echo "********************* NEW LOADTEST ************************"
-    update_pid_and_pss
-    for replication in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096
-    do
-        echo "**** Starting test at replication ${replication} ****"
-
-        # (1) Restart statsd. This will ensure its state is empty.
-        kill_statsd
-        sleep 3 # wait a bit for it to restart
-        update_pid_and_pss
-        echo "Before the test, statsd's PSS is ${pss}"
-
-        # (2) Set the replication.
-        set_replication "${replication}"
-        sleep 1 # wait a bit
-
-        # (3) Start the loadtest.
-        start_loadtest
-
-        # (4) Wait several seconds, then read the PSS.
-        sleep 100 && update_pid_and_pss
-        echo "During the test, statsd's PSS is ${pss}"
-        values+=(${pss})
-
-        echo "Values: ${values[@]}"
-
-        # (5) Stop loadtest.
-        stop_loadtest
-        sleep 2
-
-        echo ""
-    done
-
-    end_time=$(date +%s)
-    echo "Completed loadtest in $((${end_time} - ${start_time})) seconds."
-
-    values_as_str=$(IFS=$'\n'; echo "${values[*]}")
-    echo "The PSS values are:"
-    echo "${values_as_str}"
-    echo ""
-}
-
-main
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java
deleted file mode 100644
index bab0c1e..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java
+++ /dev/null
@@ -1,56 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import java.text.ParseException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class BatteryDataRecorder extends PerfDataRecorder {
-    private static final String TAG = "loadtest.BatteryDataRecorder";
-    private static final String DUMP_FILENAME = TAG + "_dump.tmp";
-
-    public BatteryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
-        int burst, boolean includeCountMetric, boolean includeDurationMetric,
-        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
-      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
-          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
-    }
-
-    @Override
-    public void startRecording(Context context) {
-        // Reset batterystats.
-        runDumpsysStats(context, DUMP_FILENAME, "batterystats", "--reset");
-    }
-
-    @Override
-    public void onAlarm(Context context) {
-        // Nothing to do as for battery, the whole data is in the final dumpsys call.
-    }
-
-    @Override
-    public void stopRecording(Context context) {
-        StringBuilder sb = new StringBuilder();
-        // Don't use --checkin.
-        runDumpsysStats(context, DUMP_FILENAME, "batterystats");
-        readDumpData(context, DUMP_FILENAME, new BatteryStatsParser(), sb);
-        writeData(context, "battery_", "time,battery_level", sb);
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java
deleted file mode 100644
index 203d97a..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java
+++ /dev/null
@@ -1,113 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-import android.util.Log;
-import java.text.ParseException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class BatteryStatsParser implements PerfParser {
-
-    private static final Pattern LINE_PATTERN =
-        Pattern.compile("\\s*\\+*(\\S*)\\s\\(\\d+\\)\\s(\\d\\d\\d)\\s.*");
-    private static final Pattern TIME_PATTERN =
-        Pattern.compile("(\\d+)?(h)?(\\d+)?(m)?(\\d+)?(s)?(\\d+)?(ms)?");
-    private static final String TAG = "loadtest.BatteryStatsParser";
-
-    private boolean mHistoryStarted;
-    private boolean mHistoryEnded;
-
-    public BatteryStatsParser() {
-    }
-
-    @Override
-    @Nullable
-    public String parseLine(String line) {
-        if (mHistoryEnded) {
-            return null;
-        }
-        if (!mHistoryStarted) {
-            if (line.contains("Battery History")) {
-                mHistoryStarted = true;
-            }
-            return null;
-        }
-        if (line.isEmpty()) {
-            mHistoryEnded = true;
-            return null;
-        }
-        Matcher lineMatcher = LINE_PATTERN.matcher(line);
-        if (lineMatcher.find() && lineMatcher.group(1) != null && lineMatcher.group(2) != null) {
-            if (lineMatcher.group(1).equals("0")) {
-                return "0," + lineMatcher.group(2) + "\n";
-            } else {
-                Matcher timeMatcher = TIME_PATTERN.matcher(lineMatcher.group(1));
-                if (timeMatcher.find()) {
-                    Long time = getTime(lineMatcher.group(1));
-                    if (time != null) {
-                        return time + "," + lineMatcher.group(2) + "\n";
-                      } else {
-                        return null; // bad time
-                    }
-                } else {
-                  return null;  // bad or no time
-                }
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    private Long getTime(String group) {
-        if ("0".equals(group)) {
-            return 0L;
-        }
-        Matcher timeMatcher = TIME_PATTERN.matcher(group);
-        if (!timeMatcher.find()) {
-            return null;
-        }
-
-        // Get rid of "ms".
-        String[] matches = group.split("ms", -1);
-        if (matches.length > 1) {
-            group = matches[0];
-        }
-
-        long time = 0L;
-        matches = group.split("h");
-        if (matches.length > 1) {
-            time += Long.parseLong(matches[0]) * 60 * 60 * 1000;  // hours
-            group = matches[1];
-        }
-        matches = group.split("m");
-        if (matches.length > 1) {
-            time += Long.parseLong(matches[0]) * 60 * 1000;  // minutes
-            group = matches[1];
-        }
-        matches = group.split("s");
-        if (matches.length > 1) {
-            time += Long.parseLong(matches[0]) * 1000; // seconds
-            group = matches[1];
-        }
-
-        if (!group.isEmpty()) {
-            time += Long.parseLong(group); // milliseconds
-        }
-        return time;
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
deleted file mode 100644
index 2e0161b..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
+++ /dev/null
@@ -1,314 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Log;
-
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.CountMetric;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
-import com.android.internal.os.StatsdConfigProto.MetricConditionLink;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Creates StatsdConfig protos for loadtesting.
- */
-public class ConfigFactory {
-    public static class ConfigMetadata {
-        public final byte[] bytes;
-        public final int numMetrics;
-
-        public ConfigMetadata(byte[] bytes, int numMetrics) {
-            this.bytes = bytes;
-            this.numMetrics = numMetrics;
-        }
-    }
-
-    public static final long CONFIG_ID = 123456789;
-
-    private static final String TAG = "loadtest.ConfigFactory";
-
-    private final StatsdConfig mTemplate;
-
-    public ConfigFactory(Context context) {
-        // Read the config template from the resoures.
-        Resources res = context.getResources();
-        byte[] template = null;
-        StatsdConfig templateProto = null;
-        try {
-            InputStream inputStream = res.openRawResource(R.raw.loadtest_config);
-            template = new byte[inputStream.available()];
-            inputStream.read(template);
-            templateProto = StatsdConfig.parseFrom(template);
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to read or parse loadtest config template. Using an empty config.");
-        }
-        mTemplate = templateProto == null ? StatsdConfig.newBuilder().build() : templateProto;
-
-        Log.d(TAG, "Loadtest template config: " + mTemplate);
-    }
-
-    /**
-     * Generates a config.
-     *
-     * All configs are based on the same template.
-     * That template is designed to make the most use of the set of atoms that {@code SequencePusher}
-     * pushes, and to exercise as many of the metrics features as possible.
-     * Furthermore, by passing a replication factor to this method, one can artificially inflate
-     * the number of metrics in the config. One can also adjust the bucket size for aggregate
-     * metrics.
-     *
-     * @param replication The number of times each metric is replicated in the config.
-     *        If the config template has n metrics, the generated config will have n * replication
-     *        ones
-     * @param bucketMillis The bucket size, in milliseconds, for aggregate metrics
-     * @param placebo If true, only return an empty config
-     * @return The serialized config and the number of metrics.
-     */
-    public ConfigMetadata getConfig(int replication, TimeUnit bucket, boolean placebo,
-            boolean includeCount, boolean includeDuration, boolean includeEvent,
-            boolean includeValue, boolean includeGauge) {
-        StatsdConfig.Builder config = StatsdConfig.newBuilder()
-            .setId(CONFIG_ID);
-        if (placebo) {
-          replication = 0;  // Config will be empty, aside from a name.
-        }
-        int numMetrics = 0;
-        for (int i = 0; i < replication; i++) {
-            // metrics
-            if (includeEvent) {
-                for (EventMetric metric : mTemplate.getEventMetricList()) {
-                    addEventMetric(metric, i, config);
-                    numMetrics++;
-                }
-            }
-            if (includeCount) {
-                for (CountMetric metric : mTemplate.getCountMetricList()) {
-                    addCountMetric(metric, i, bucket, config);
-                    numMetrics++;
-                }
-            }
-            if (includeDuration) {
-                for (DurationMetric metric : mTemplate.getDurationMetricList()) {
-                    addDurationMetric(metric, i, bucket, config);
-                    numMetrics++;
-                }
-            }
-            if (includeGauge) {
-                for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
-                    addGaugeMetric(metric, i, bucket, config);
-                    numMetrics++;
-                }
-            }
-            if (includeValue) {
-                for (ValueMetric metric : mTemplate.getValueMetricList()) {
-                    addValueMetric(metric, i, bucket, config);
-                    numMetrics++;
-                }
-            }
-            // predicates
-            for (Predicate predicate : mTemplate.getPredicateList()) {
-              addPredicate(predicate, i, config);
-            }
-            // matchers
-            for (AtomMatcher matcher : mTemplate.getAtomMatcherList()) {
-              addMatcher(matcher, i, config);
-            }
-        }
-
-        Log.d(TAG, "Loadtest config is : " + config.build());
-        Log.d(TAG, "Generated config has " + numMetrics + " metrics");
-
-        return new ConfigMetadata(config.build().toByteArray(), numMetrics);
-    }
-
-    /**
-     * Creates {@link MetricConditionLink}s that are identical to the one passed to this method,
-     * except that the names are appended with the provided suffix.
-     */
-    private List<MetricConditionLink> getLinks(
-        List<MetricConditionLink> links, int suffix) {
-        List<MetricConditionLink> newLinks = new ArrayList();
-        for (MetricConditionLink link : links) {
-            newLinks.add(link.toBuilder()
-                .setCondition(link.getCondition() + suffix)
-                .build());
-        }
-        return newLinks;
-    }
-
-    /**
-     * Creates an {@link EventMetric} based on the template. Makes sure that all names are appended
-     * with the provided suffix. Then adds that metric to the config.
-     */
-    private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) {
-        EventMetric.Builder metric = template.toBuilder()
-            .setId(template.getId() + suffix)
-            .setWhat(template.getWhat() + suffix);
-        if (template.hasCondition()) {
-            metric.setCondition(template.getCondition() + suffix);
-        }
-        if (template.getLinksCount() > 0) {
-            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
-            metric.clearLinks();
-            metric.addAllLinks(links);
-        }
-        config.addEventMetric(metric);
-    }
-
-    /**
-     * Creates a {@link CountMetric} based on the template. Makes sure that all names are appended
-     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
-     */
-    private void addCountMetric(CountMetric template, int suffix, TimeUnit bucket,
-        StatsdConfig.Builder config) {
-        CountMetric.Builder metric = template.toBuilder()
-            .setId(template.getId() + suffix)
-            .setWhat(template.getWhat() + suffix);
-        if (template.hasCondition()) {
-            metric.setCondition(template.getCondition() + suffix);
-        }
-        if (template.getLinksCount() > 0) {
-            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
-            metric.clearLinks();
-            metric.addAllLinks(links);
-        }
-        metric.setBucket(bucket);
-        config.addCountMetric(metric);
-    }
-
-    /**
-     * Creates a {@link DurationMetric} based on the template. Makes sure that all names are appended
-     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
-     */
-    private void addDurationMetric(DurationMetric template, int suffix, TimeUnit bucket,
-        StatsdConfig.Builder config) {
-        DurationMetric.Builder metric = template.toBuilder()
-            .setId(template.getId() + suffix)
-            .setWhat(template.getWhat() + suffix);
-        if (template.hasCondition()) {
-            metric.setCondition(template.getCondition() + suffix);
-        }
-        if (template.getLinksCount() > 0) {
-            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
-            metric.clearLinks();
-            metric.addAllLinks(links);
-        }
-        metric.setBucket(bucket);
-        config.addDurationMetric(metric);
-    }
-
-    /**
-     * Creates a {@link GaugeMetric} based on the template. Makes sure that all names are appended
-     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
-     */
-    private void addGaugeMetric(GaugeMetric template, int suffix, TimeUnit bucket,
-        StatsdConfig.Builder config) {
-        GaugeMetric.Builder metric = template.toBuilder()
-            .setId(template.getId() + suffix)
-            .setWhat(template.getWhat() + suffix);
-        if (template.hasCondition()) {
-            metric.setCondition(template.getCondition() + suffix);
-        }
-        if (template.getLinksCount() > 0) {
-            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
-            metric.clearLinks();
-            metric.addAllLinks(links);
-        }
-        metric.setBucket(bucket);
-        config.addGaugeMetric(metric);
-    }
-
-    /**
-     * Creates a {@link ValueMetric} based on the template. Makes sure that all names are appended
-     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
-     */
-    private void addValueMetric(ValueMetric template, int suffix, TimeUnit bucket,
-        StatsdConfig.Builder config) {
-        ValueMetric.Builder metric = template.toBuilder()
-            .setId(template.getId() + suffix)
-            .setWhat(template.getWhat() + suffix);
-        if (template.hasCondition()) {
-            metric.setCondition(template.getCondition() + suffix);
-        }
-        if (template.getLinksCount() > 0) {
-            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
-            metric.clearLinks();
-            metric.addAllLinks(links);
-        }
-        metric.setBucket(bucket);
-        config.addValueMetric(metric);
-    }
-
-    /**
-     * Creates a {@link Predicate} based on the template. Makes sure that all names
-     * are appended with the provided suffix. Then adds that predicate to the config.
-     */
-    private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) {
-        Predicate.Builder predicate = template.toBuilder()
-            .setId(template.getId() + suffix);
-        if (template.hasCombination()) {
-            Predicate.Combination.Builder cb = template.getCombination().toBuilder()
-                .clearPredicate();
-            for (long child : template.getCombination().getPredicateList()) {
-                cb.addPredicate(child + suffix);
-            }
-            predicate.setCombination(cb.build());
-        }
-        if (template.hasSimplePredicate()) {
-            SimplePredicate.Builder sc = template.getSimplePredicate().toBuilder()
-                .setStart(template.getSimplePredicate().getStart() + suffix)
-                .setStop(template.getSimplePredicate().getStop() + suffix);
-            if (template.getSimplePredicate().hasStopAll()) {
-                sc.setStopAll(template.getSimplePredicate().getStopAll() + suffix);
-            }
-            predicate.setSimplePredicate(sc.build());
-        }
-        config.addPredicate(predicate);
-    }
-
-    /**
-     * Creates a {@link AtomMatcher} based on the template. Makes sure that all names
-     * are appended with the provided suffix. Then adds that matcher to the config.
-     */
-    private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
-        AtomMatcher.Builder matcher = template.toBuilder()
-            .setId(template.getId() + suffix);
-        if (template.hasCombination()) {
-            AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
-                .clearMatcher();
-            for (long child : template.getCombination().getMatcherList()) {
-                cb.addMatcher(child + suffix);
-            }
-            matcher.setCombination(cb);
-        }
-        config.addAtomMatcher(matcher);
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
deleted file mode 100644
index d55f3f3..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
+++ /dev/null
@@ -1,169 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.text.format.DateFormat;
-
-import com.android.os.StatsLog;
-
-import java.util.List;
-
-public class DisplayProtoUtils {
-    private static final int MAX_NUM_METRICS_TO_DISPLAY = 10;
-
-    public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReportList reports) {
-        sb.append("******************** Report ********************\n");
-        if (reports.hasConfigKey()) {
-            sb.append("ConfigKey: ");
-            com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
-            sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId())
-                    .append("\n");
-        }
-
-        int numMetrics = 0;
-        for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) {
-            sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
-            sb.append("Last report time:").append(getDateStr(report.getLastReportElapsedNanos())).
-                    append("\n");
-            sb.append("Current report time:").append(getDateStr(report.getCurrentReportElapsedNanos())).
-                    append("\n");
-            for (StatsLog.StatsLogReport log : report.getMetricsList()) {
-                numMetrics++;
-                if (numMetrics > MAX_NUM_METRICS_TO_DISPLAY) {
-                    sb.append("... output truncated\n");
-                    sb.append("************************************************");
-                    return;
-                }
-                sb.append("\n");
-                sb.append("metric id: ").append(log.getMetricId()).append("\n");
-
-                switch (log.getDataCase()) {
-                    case DURATION_METRICS:
-                        sb.append("Duration metric data\n");
-                        displayDurationMetricData(sb, log);
-                        break;
-                    case EVENT_METRICS:
-                        sb.append("Event metric data\n");
-                        displayEventMetricData(sb, log);
-                        break;
-                    case COUNT_METRICS:
-                        sb.append("Count metric data\n");
-                        displayCountMetricData(sb, log);
-                        break;
-                    case GAUGE_METRICS:
-                        sb.append("Gauge metric data\n");
-                        displayGaugeMetricData(sb, log);
-                        break;
-                    case VALUE_METRICS:
-                        sb.append("Value metric data\n");
-                        displayValueMetricData(sb, log);
-                        break;
-                    case DATA_NOT_SET:
-                        sb.append("No metric data\n");
-                        break;
-                }
-            }
-        }
-        sb.append("************************************************");
-    }
-
-    public static String getDateStr(long nanoSec) {
-        return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
-    }
-
-    private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
-        sb.append(dimensionValue.getField()).append(":");
-        if (dimensionValue.hasValueBool()) {
-            sb.append(dimensionValue.getValueBool());
-        } else if (dimensionValue.hasValueFloat()) {
-            sb.append(dimensionValue.getValueFloat());
-        } else if (dimensionValue.hasValueInt()) {
-            sb.append(dimensionValue.getValueInt());
-        } else if (dimensionValue.hasValueStr()) {
-            sb.append(dimensionValue.getValueStr());
-        } else if (dimensionValue.hasValueTuple()) {
-            sb.append("{");
-            for (StatsLog.DimensionsValue child :
-                    dimensionValue.getValueTuple().getDimensionsValueList()) {
-                displayDimension(sb, child);
-            }
-            sb.append("}");
-        }
-        sb.append(" ");
-    }
-
-    public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper
-                = log.getDurationMetrics();
-        sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
-        for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
-            sb.append("dimension_in_what: ");
-            displayDimension(sb, duration.getDimensionsInWhat());
-            sb.append("\n");
-            if (duration.hasDimensionsInCondition()) {
-                sb.append("dimension_in_condition: ");
-                displayDimension(sb, duration.getDimensionsInCondition());
-                sb.append("\n");
-            }
-
-            for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList())  {
-                sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-")
-                        .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ")
-                        .append(info.getDurationNanos()).append(" ns\n");
-            }
-        }
-    }
-
-    public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n");
-        StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper =
-                log.getEventMetrics();
-        for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) {
-            sb.append(getDateStr(event.getElapsedTimestampNanos())).append(": ");
-            sb.append(event.getAtom().getPushedCase().toString()).append("\n");
-        }
-    }
-
-    public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper
-                = log.getCountMetrics();
-        sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
-        for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
-            sb.append("dimension_in_what: ");
-            displayDimension(sb, count.getDimensionsInWhat());
-            sb.append("\n");
-            if (count.hasDimensionsInCondition()) {
-                sb.append("dimension_in_condition: ");
-                displayDimension(sb, count.getDimensionsInCondition());
-                sb.append("\n");
-            }
-
-            for (StatsLog.CountBucketInfo info : count.getBucketInfoList())  {
-                sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-")
-                        .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ")
-                        .append(info.getCount()).append("\n");
-            }
-        }
-    }
-
-    public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Display me!");
-    }
-
-    public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
-        sb.append("Display me!");
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
deleted file mode 100644
index 769f78c..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ /dev/null
@@ -1,756 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.StatsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IStatsManager;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.util.StatsLog;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.view.MotionEvent;
-import android.view.View.OnFocusChangeListener;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Runs a load test for statsd.
- * How it works:
- * <ul>
- * <li> Sets up and pushes a custom config with metrics that exercise a large swath of code paths.
- * <li> Periodically logs certain atoms into logd.
- * <li> Impact on battery can be printed to logcat, or a bug report can be filed and analyzed
- * in battery Historian.
- * </ul>
- * The load depends on how demanding the config is, as well as how frequently atoms are pushsed
- * to logd. Those are all controlled by 4 adjustable parameters:
- * <ul>
- * <li> The 'replication' parameter artificially multiplies the number of metrics in the config.
- * <li> The bucket size controls the time-bucketing the aggregate metrics.
- * <li> The period parameter controls how frequently atoms are pushed to logd.
- * <li> The 'burst' parameter controls how many atoms are pushed at the same time (per period).
- * </ul>
- */
-public class LoadtestActivity extends Activity implements AdapterView.OnItemSelectedListener {
-
-    private static final String TAG = "loadtest.LoadtestActivity";
-    public static final String TYPE = "type";
-    private static final String PUSH_ALARM = "push_alarm";
-    public static final String PERF_ALARM = "perf_alarm";
-    private static final String SET_REPLICATION = "set_replication";
-    private static final String REPLICATION = "replication";
-    private static final String START = "start";
-    private static final String STOP = "stop";
-    private static final Map<String, TimeUnit> TIME_UNIT_MAP = initializeTimeUnitMap();
-    private static final List<String> TIME_UNIT_LABELS = initializeTimeUnitLabels();
-
-    public final static class PusherAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Intent activityIntent = new Intent(context, LoadtestActivity.class);
-            activityIntent.putExtra(TYPE, PUSH_ALARM);
-            context.startActivity(activityIntent);
-        }
-    }
-
-    public final static class StopperAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Intent activityIntent = new Intent(context, LoadtestActivity.class);
-            activityIntent.putExtra(TYPE, STOP);
-            context.startActivity(activityIntent);
-        }
-    }
-
-    private static Map<String, TimeUnit> initializeTimeUnitMap() {
-        Map<String, TimeUnit> labels = new HashMap();
-        labels.put("1m", TimeUnit.ONE_MINUTE);
-        labels.put("5m", TimeUnit.FIVE_MINUTES);
-        labels.put("10m", TimeUnit.TEN_MINUTES);
-        labels.put("30m", TimeUnit.THIRTY_MINUTES);
-        labels.put("1h", TimeUnit.ONE_HOUR);
-        labels.put("3h", TimeUnit.THREE_HOURS);
-        labels.put("6h", TimeUnit.SIX_HOURS);
-        labels.put("12h", TimeUnit.TWELVE_HOURS);
-        labels.put("1d", TimeUnit.ONE_DAY);
-        labels.put("1s", TimeUnit.CTS);
-        return labels;
-    }
-
-    private static List<String> initializeTimeUnitLabels() {
-        List<String> labels = new ArrayList();
-        labels.add("1s");
-        labels.add("1m");
-        labels.add("5m");
-        labels.add("10m");
-        labels.add("30m");
-        labels.add("1h");
-        labels.add("3h");
-        labels.add("6h");
-        labels.add("12h");
-        labels.add("1d");
-        return labels;
-    }
-
-    private AlarmManager mAlarmMgr;
-
-    /**
-     * Used to periodically log atoms to logd.
-     */
-    private PendingIntent mPushPendingIntent;
-
-    /**
-     * Used to end the loadtest.
-     */
-    private PendingIntent mStopPendingIntent;
-
-    private Button mStartStop;
-    private EditText mReplicationText;
-    private Spinner mBucketSpinner;
-    private EditText mPeriodText;
-    private EditText mBurstText;
-    private EditText mDurationText;
-    private TextView mReportText;
-    private CheckBox mPlaceboCheckBox;
-    private CheckBox mCountMetricCheckBox;
-    private CheckBox mDurationMetricCheckBox;
-    private CheckBox mEventMetricCheckBox;
-    private CheckBox mValueMetricCheckBox;
-    private CheckBox mGaugeMetricCheckBox;
-
-    /**
-     * When the load test started.
-     */
-    private long mStartedTimeMillis;
-
-    /**
-     * For measuring perf data.
-     */
-    private PerfData mPerfData;
-
-    /**
-     * For communicating with statsd.
-     */
-    private StatsManager mStatsManager;
-
-    private PowerManager mPowerManager;
-    private WakeLock mWakeLock;
-
-    /**
-     * If true, we only measure the effect of the loadtest infrastructure. No atom are pushed and
-     * the configuration is empty.
-     */
-    private boolean mPlacebo;
-
-    /**
-     * Whether to include CountMetric in the config.
-     */
-    private boolean mIncludeCountMetric;
-
-    /**
-     * Whether to include DurationMetric in the config.
-     */
-    private boolean mIncludeDurationMetric;
-
-    /**
-     * Whether to include EventMetric in the config.
-     */
-    private boolean mIncludeEventMetric;
-
-    /**
-     * Whether to include ValueMetric in the config.
-     */
-    private boolean mIncludeValueMetric;
-
-    /**
-     * Whether to include GaugeMetric in the config.
-     */
-    private boolean mIncludeGaugeMetric;
-
-    /**
-     * The burst size.
-     */
-    private int mBurst;
-
-    /**
-     * The metrics replication.
-     */
-    private int mReplication;
-
-    /**
-     * The period, in seconds, at which batches of atoms are pushed.
-     */
-    private long mPeriodSecs;
-
-    /**
-     * The bucket size, in minutes, for aggregate metrics.
-     */
-    private TimeUnit mBucket;
-
-    /**
-     * The duration, in minutes, of the loadtest.
-     */
-    private long mDurationMins;
-
-    /**
-     * Whether the loadtest has started.
-     */
-    private boolean mStarted = false;
-
-    /**
-     * Orchestrates the logging of pushed events into logd.
-     */
-    private SequencePusher mPusher;
-
-    /**
-     * Generates statsd configs.
-     */
-    private ConfigFactory mFactory;
-
-    /**
-     * For intra-minute periods.
-     */
-    private final Handler mHandler = new Handler();
-
-    /**
-     * Number of metrics in the current config.
-     */
-    private int mNumMetrics;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Log.d(TAG, "Starting loadtest Activity");
-
-        setContentView(R.layout.activity_loadtest);
-        mReportText = (TextView) findViewById(R.id.report_text);
-        initBurst();
-        initReplication();
-        initBucket();
-        initPeriod();
-        initDuration();
-        initPlacebo();
-        initMetricWhitelist();
-
-        // Hide the keyboard outside edit texts.
-        findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                InputMethodManager imm =
-                        (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-                if (getCurrentFocus() != null) {
-                    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
-                }
-                return true;
-            }
-        });
-
-        mStartStop = findViewById(R.id.start_stop);
-        mStartStop.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                if (mStarted) {
-                    stopLoadtest();
-                } else {
-                    startLoadtest();
-                }
-            }
-        });
-
-        mAlarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
-        mStatsManager = (StatsManager) getSystemService("stats");
-        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
-        mFactory = new ConfigFactory(this);
-        stopLoadtest();
-        mReportText.setText("");
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        String type = intent.getStringExtra(TYPE);
-        if (type == null) {
-            return;
-        }
-        switch (type) {
-            case PERF_ALARM:
-                onPerfAlarm();
-                break;
-            case PUSH_ALARM:
-                onAlarm();
-                break;
-            case SET_REPLICATION:
-                if (intent.hasExtra(REPLICATION)) {
-                    setReplication(intent.getIntExtra(REPLICATION, 0));
-                }
-                break;
-            case START:
-                startLoadtest();
-                break;
-            case STOP:
-                stopLoadtest();
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown type: " + type);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.d(TAG, "Destroying");
-        mPerfData.onDestroy();
-        stopLoadtest();
-        clearConfigs();
-        super.onDestroy();
-    }
-
-    @Nullable
-    public StatsdStatsReport getMetadata() {
-        if (!statsdRunning()) {
-            return null;
-        }
-        if (mStatsManager != null) {
-            byte[] data;
-            try {
-                data = mStatsManager.getStatsMetadata();
-            } catch (StatsManager.StatsUnavailableException e) {
-                Log.e(TAG, "Failed to get data from statsd", e);
-                return null;
-            }
-            if (data != null) {
-                StatsdStatsReport report = null;
-                boolean good = false;
-                try {
-                    return StatsdStatsReport.parseFrom(data);
-                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-                    Log.d(TAG, "Bad StatsdStatsReport");
-                }
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    public List<ConfigMetricsReport> getData() {
-        if (!statsdRunning()) {
-            return null;
-        }
-        if (mStatsManager != null) {
-            byte[] data;
-            try {
-                data = mStatsManager.getReports(ConfigFactory.CONFIG_ID);
-            } catch (StatsManager.StatsUnavailableException e) {
-                Log.e(TAG, "Failed to get data from statsd", e);
-                return null;
-            }
-            if (data != null) {
-                ConfigMetricsReportList reports = null;
-                try {
-                    reports = ConfigMetricsReportList.parseFrom(data);
-                    Log.d(TAG, "Num reports: " + reports.getReportsCount());
-                    StringBuilder sb = new StringBuilder();
-                    DisplayProtoUtils.displayLogReport(sb, reports);
-                    Log.d(TAG, sb.toString());
-                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-                    Log.d(TAG, "Invalid data");
-                }
-                if (reports != null) {
-                    return reports.getReportsList();
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        String item = parent.getItemAtPosition(position).toString();
-
-        mBucket = TIME_UNIT_MAP.get(item);
-    }
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-        // Another interface callback
-    }
-
-    private void onPerfAlarm() {
-        if (mPerfData != null) {
-            mPerfData.onAlarm(this);
-        }
-        // Piggy-back on that alarm to show the elapsed time.
-        long elapsedTimeMins = (long) Math.floor(
-                (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
-        mReportText.setText("Loadtest in progress.\n"
-                + "num metrics =" + mNumMetrics
-                + "\nElapsed time = " + elapsedTimeMins + " min(s)");
-    }
-
-    private void onAlarm() {
-        Log.d(TAG, "ON ALARM");
-
-        // Set the next task.
-        scheduleNext();
-
-        // Do the work.
-        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StatsdLoadTest");
-        mWakeLock.acquire();
-        if (mPusher != null) {
-            mPusher.next();
-        }
-        mWakeLock.release();
-        mWakeLock = null;
-    }
-
-    /**
-     * Schedules the next cycle of pushing atoms into logd.
-     */
-    private void scheduleNext() {
-        Intent intent = new Intent(this, PusherAlarmReceiver.class);
-        intent.putExtra(TYPE, PUSH_ALARM);
-        mPushPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
-        long nextTime = SystemClock.elapsedRealtime() + mPeriodSecs * 1000;
-        mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mPushPendingIntent);
-    }
-
-    private synchronized void startLoadtest() {
-        if (mStarted) {
-            return;
-        }
-
-        // Clean up the state.
-        stopLoadtest();
-
-        // Prepare to push a sequence of atoms to logd.
-        mPusher = new SequencePusher(mBurst, mPlacebo);
-
-        // Create a config and push it to statsd.
-        if (!setConfig(mFactory.getConfig(mReplication, mBucket, mPlacebo,
-                mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric,
-                mIncludeValueMetric, mIncludeGaugeMetric))) {
-            return;
-        }
-
-        // Remember to stop in the future.
-        Intent intent = new Intent(this, StopperAlarmReceiver.class);
-        intent.putExtra(TYPE, STOP);
-        mStopPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
-        long nextTime = SystemClock.elapsedRealtime() + mDurationMins * 60 * 1000;
-        mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mStopPendingIntent);
-
-        // Log atoms.
-        scheduleNext();
-
-        // Start tracking performance.
-        mPerfData = new PerfData(this, mPlacebo, mReplication, mBucket, mPeriodSecs, mBurst,
-                mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, mIncludeValueMetric,
-                mIncludeGaugeMetric);
-        mPerfData.startRecording(this);
-
-        mReportText.setText("Loadtest in progress.\nnum metrics =" + mNumMetrics);
-        mStartedTimeMillis = SystemClock.elapsedRealtime();
-
-        updateStarted(true);
-    }
-
-    private synchronized void stopLoadtest() {
-        if (mPushPendingIntent != null) {
-            Log.d(TAG, "Canceling pre-existing push alarm");
-            mAlarmMgr.cancel(mPushPendingIntent);
-            mPushPendingIntent = null;
-        }
-        if (mStopPendingIntent != null) {
-            Log.d(TAG, "Canceling pre-existing stop alarm");
-            mAlarmMgr.cancel(mStopPendingIntent);
-            mStopPendingIntent = null;
-        }
-        if (mWakeLock != null) {
-            mWakeLock.release();
-            mWakeLock = null;
-        }
-        if (mPerfData != null) {
-            mPerfData.stopRecording(this);
-            mPerfData.onDestroy();
-            mPerfData = null;
-        }
-
-        // Obtain the latest data and display it.
-        getData();
-
-        long elapsedTimeMins = (long) Math.floor(
-                (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
-        mReportText.setText("Loadtest ended. Elapsed time = " + elapsedTimeMins + " min(s)");
-        clearConfigs();
-        updateStarted(false);
-    }
-
-    private synchronized void updateStarted(boolean started) {
-        mStarted = started;
-        mStartStop.setBackgroundColor(started ?
-                Color.parseColor("#FFFF0000") : Color.parseColor("#FF00FF00"));
-        mStartStop.setText(started ? getString(R.string.stop) : getString(R.string.start));
-        updateControlsEnabled();
-    }
-
-    private void updateControlsEnabled() {
-        mBurstText.setEnabled(!mPlacebo && !mStarted);
-        mReplicationText.setEnabled(!mPlacebo && !mStarted);
-        mPeriodText.setEnabled(!mStarted);
-        mBucketSpinner.setEnabled(!mPlacebo && !mStarted);
-        mDurationText.setEnabled(!mStarted);
-        mPlaceboCheckBox.setEnabled(!mStarted);
-
-        boolean enabled = !mStarted && !mPlaceboCheckBox.isChecked();
-        mCountMetricCheckBox.setEnabled(enabled);
-        mDurationMetricCheckBox.setEnabled(enabled);
-        mEventMetricCheckBox.setEnabled(enabled);
-        mValueMetricCheckBox.setEnabled(enabled);
-        mGaugeMetricCheckBox.setEnabled(enabled);
-    }
-
-    private boolean statsdRunning() {
-        if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) {
-            Log.d(TAG, "Statsd not running");
-            Toast.makeText(LoadtestActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show();
-            return false;
-        }
-        return true;
-    }
-
-    private int sanitizeInt(int val, int min, int max) {
-        if (val > max) {
-            val = max;
-        } else if (val < min) {
-            val = min;
-        }
-        return val;
-    }
-
-    private void clearConfigs() {
-        // TODO: Clear all configs instead of specific ones.
-        if (mStatsManager != null) {
-            if (mStarted) {
-                try {
-                    mStatsManager.removeConfig(ConfigFactory.CONFIG_ID);
-                    Log.d(TAG, "Removed loadtest statsd configs.");
-                } catch (StatsManager.StatsUnavailableException e) {
-                    Log.e(TAG, "Failed to remove loadtest configs.", e);
-                }
-            }
-        }
-    }
-
-    private boolean setConfig(ConfigFactory.ConfigMetadata configData) {
-        if (mStatsManager != null) {
-            try {
-                mStatsManager.addConfig(ConfigFactory.CONFIG_ID, configData.bytes);
-                mNumMetrics = configData.numMetrics;
-                Log.d(TAG, "Config pushed to statsd");
-                return true;
-            } catch (StatsManager.StatsUnavailableException | IllegalArgumentException e) {
-                Log.e(TAG, "Failed to push config to statsd", e);
-            }
-        }
-        return false;
-    }
-
-    private synchronized void setReplication(int replication) {
-        if (mStarted) {
-            return;
-        }
-        mReplicationText.setText("" + replication);
-    }
-
-    private synchronized void setPeriodSecs(long periodSecs) {
-        mPeriodSecs = periodSecs;
-    }
-
-    private synchronized void setBurst(int burst) {
-        mBurst = burst;
-    }
-
-    private synchronized void setDurationMins(long durationMins) {
-        mDurationMins = durationMins;
-    }
-
-
-    private void handleFocus(EditText editText) {
-      /*
-        editText.setOnFocusChangeListener(new OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (!hasFocus && editText.getText().toString().isEmpty()) {
-                    editText.setText("-1", TextView.BufferType.EDITABLE);
-                }
-            }
-        });
-      */
-    }
-
-    private void initBurst() {
-        mBurst = getResources().getInteger(R.integer.burst_default);
-        mBurstText = (EditText) findViewById(R.id.burst);
-        mBurstText.addTextChangedListener(new NumericalWatcher(mBurstText, 0, 1000) {
-            @Override
-            public void onNewValue(int newValue) {
-                setBurst(newValue);
-            }
-        });
-        handleFocus(mBurstText);
-    }
-
-    private void initReplication() {
-        mReplication = getResources().getInteger(R.integer.replication_default);
-        mReplicationText = (EditText) findViewById(R.id.replication);
-        mReplicationText.addTextChangedListener(new NumericalWatcher(mReplicationText, 1, 4096) {
-            @Override
-            public void onNewValue(int newValue) {
-                mReplication = newValue;
-            }
-        });
-        handleFocus(mReplicationText);
-    }
-
-    private void initBucket() {
-        String defaultValue = getResources().getString(R.string.bucket_default);
-        mBucket = TimeUnit.valueOf(defaultValue);
-        mBucketSpinner = (Spinner) findViewById(R.id.bucket_spinner);
-
-        ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(
-                this, R.layout.spinner_item, TIME_UNIT_LABELS);
-
-        mBucketSpinner.setAdapter(dataAdapter);
-        mBucketSpinner.setOnItemSelectedListener(this);
-
-        for (String label : TIME_UNIT_MAP.keySet()) {
-            if (defaultValue.equals(TIME_UNIT_MAP.get(label).toString())) {
-                mBucketSpinner.setSelection(dataAdapter.getPosition(label));
-            }
-        }
-    }
-
-    private void initPeriod() {
-        mPeriodSecs = getResources().getInteger(R.integer.period_default);
-        mPeriodText = (EditText) findViewById(R.id.period);
-        mPeriodText.addTextChangedListener(new NumericalWatcher(mPeriodText, 1, 60) {
-            @Override
-            public void onNewValue(int newValue) {
-                setPeriodSecs(newValue);
-            }
-        });
-        handleFocus(mPeriodText);
-    }
-
-    private void initDuration() {
-        mDurationMins = getResources().getInteger(R.integer.duration_default);
-        mDurationText = (EditText) findViewById(R.id.duration);
-        mDurationText.addTextChangedListener(new NumericalWatcher(mDurationText, 1, 24 * 60) {
-            @Override
-            public void onNewValue(int newValue) {
-                setDurationMins(newValue);
-            }
-        });
-        handleFocus(mDurationText);
-    }
-
-    private void initPlacebo() {
-        mPlaceboCheckBox = findViewById(R.id.placebo);
-        mPlacebo = false;
-        mPlaceboCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mPlacebo = mPlaceboCheckBox.isChecked();
-                updateControlsEnabled();
-            }
-        });
-    }
-
-    private void initMetricWhitelist() {
-        mCountMetricCheckBox = findViewById(R.id.include_count);
-        mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mIncludeCountMetric = mCountMetricCheckBox.isChecked();
-            }
-        });
-        mDurationMetricCheckBox = findViewById(R.id.include_duration);
-        mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
-            }
-        });
-        mEventMetricCheckBox = findViewById(R.id.include_event);
-        mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mIncludeEventMetric = mEventMetricCheckBox.isChecked();
-            }
-        });
-        mValueMetricCheckBox = findViewById(R.id.include_value);
-        mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mIncludeValueMetric = mValueMetricCheckBox.isChecked();
-            }
-        });
-        mGaugeMetricCheckBox = findViewById(R.id.include_gauge);
-        mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
-            }
-        });
-
-        mIncludeCountMetric = mCountMetricCheckBox.isChecked();
-        mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
-        mIncludeEventMetric = mEventMetricCheckBox.isChecked();
-        mIncludeValueMetric = mValueMetricCheckBox.isChecked();
-        mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java
deleted file mode 100644
index 01eebf2..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java
+++ /dev/null
@@ -1,69 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.util.Log;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Parses PSS info from dumpsys meminfo */
-public class MemInfoParser implements PerfParser {
-
-    private static final Pattern LINE_PATTERN =
-        Pattern.compile("\\s*(\\d*,*\\d*)K:\\s(\\S*)\\s\\.*");
-    private static final String PSS_BY_PROCESS = "Total PSS by process:";
-    private static final String TAG = "loadtest.MemInfoParser";
-
-    private boolean mPssStarted;
-    private boolean mPssEnded;
-    private final long mStartTimeMillis;
-
-    public MemInfoParser(long startTimeMillis) {
-        mStartTimeMillis = startTimeMillis;
-    }
-
-    @Override
-    @Nullable
-    public String parseLine(String line) {
-        if (mPssEnded) {
-            return null;
-        }
-        if (!mPssStarted) {
-            if (line.contains(PSS_BY_PROCESS)) {
-                mPssStarted = true;
-            }
-            return null;
-        }
-        if (line.isEmpty()) {
-            mPssEnded = true;
-            return null;
-        }
-        Matcher lineMatcher = LINE_PATTERN.matcher(line);
-        if (lineMatcher.find() && lineMatcher.group(1) != null && lineMatcher.group(2) != null) {
-            if (lineMatcher.group(2).equals("statsd")) {
-                long timeDeltaMillis = SystemClock.elapsedRealtime() - mStartTimeMillis;
-                return timeDeltaMillis + "," + convertToPss(lineMatcher.group(1));
-            }
-        }
-        return null;
-    }
-
-    private String convertToPss(String input) {
-        return input.replace(",", "");
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java
deleted file mode 100644
index af7bd4d..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java
+++ /dev/null
@@ -1,53 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.util.Log;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-
-public class MemoryDataRecorder extends PerfDataRecorder {
-    private static final String TAG = "loadtest.MemoryDataDataRecorder";
-    private static final String DUMP_FILENAME = TAG + "_dump.tmp";
-
-    private long mStartTimeMillis;
-    private StringBuilder mSb;
-
-    public MemoryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
-        int burst,  boolean includeCountMetric, boolean includeDurationMetric,
-        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
-      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
-          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
-    }
-
-    @Override
-    public void startRecording(Context context) {
-        mStartTimeMillis = SystemClock.elapsedRealtime();
-        mSb = new StringBuilder();
-    }
-
-    @Override
-    public void onAlarm(Context context) {
-        runDumpsysStats(context, DUMP_FILENAME, "meminfo");
-        readDumpData(context, DUMP_FILENAME, new MemInfoParser(mStartTimeMillis), mSb);
-    }
-
-    @Override
-    public void stopRecording(Context context) {
-        writeData(context, "meminfo_", "time,pss", mSb);
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java
deleted file mode 100644
index 555e6dd..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java
+++ /dev/null
@@ -1,70 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.widget.TextView;
-
-public abstract class NumericalWatcher implements TextWatcher {
-
-    private static final String TAG = "loadtest.NumericalWatcher";
-
-    private final TextView mTextView;
-    private final int mMin;
-    private final int mMax;
-    private int currentValue = -1;
-
-    public NumericalWatcher(TextView textView, int min, int max) {
-        mTextView = textView;
-        mMin = min;
-        mMax = max;
-    }
-
-    public abstract void onNewValue(int newValue);
-
-    @Override
-    final public void afterTextChanged(Editable editable) {
-        String s = mTextView.getText().toString();
-        if (s.isEmpty()) {
-          return;
-        }
-        int unsanitized = Integer.parseInt(s);
-        int newValue = sanitize(unsanitized);
-        if (currentValue != newValue || unsanitized != newValue) {
-            currentValue = newValue;
-            editable.clear();
-            editable.append(newValue + "");
-        }
-        onNewValue(newValue);
-    }
-
-    @Override
-    final public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
-
-    @Override
-    final public void onTextChanged(CharSequence s, int start, int before, int count) {}
-
-    private int sanitize(int val) {
-        if (val > mMax) {
-            val = mMax;
-        } else if (val < mMin) {
-            val = mMin;
-        }
-        return val;
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java
deleted file mode 100644
index 7a01ade..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java
+++ /dev/null
@@ -1,116 +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.
- */
-package com.android.statsd.loadtest;
-
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** Prints some information about the device via Dumpsys in order to evaluate health metrics. */
-public class PerfData extends PerfDataRecorder {
-
-    private static final String TAG = "loadtest.PerfData";
-
-    /** Polling period for performance snapshots like memory. */
-    private static final long POLLING_PERIOD_MILLIS = 1 * 60 * 1000;
-
-    public final static class PerfAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Intent activityIntent = new Intent(context, LoadtestActivity.class);
-            activityIntent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM);
-            context.startActivity(activityIntent);
-         }
-    }
-
-    private AlarmManager mAlarmMgr;
-
-    /** Used to periodically poll some dumpsys data. */
-    private PendingIntent mPendingIntent;
-
-    private final Set<PerfDataRecorder> mRecorders;
-
-    public PerfData(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        TimeUnit bucket, long periodSecs,  int burst, boolean includeCountMetric,
-        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
-        boolean includeGaugeMetric) {
-      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
-          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
-        mRecorders = new HashSet();
-        mRecorders.add(new BatteryDataRecorder(placebo, replication, bucket, periodSecs, burst,
-                includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
-                includeGaugeMetric));
-        mRecorders.add(new MemoryDataRecorder(placebo, replication, bucket, periodSecs, burst,
-                includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
-                includeGaugeMetric));
-        mRecorders.add(new StatsdStatsRecorder(loadtestActivity, placebo, replication, bucket,
-                periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric,
-                includeValueMetric, includeGaugeMetric));
-        mRecorders.add(new ValidationRecorder(loadtestActivity, placebo, replication, bucket,
-                periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric,
-                includeValueMetric, includeGaugeMetric));
-        mAlarmMgr = (AlarmManager) loadtestActivity.getSystemService(Context.ALARM_SERVICE);
-    }
-
-    public void onDestroy() {
-        if (mPendingIntent != null) {
-            mAlarmMgr.cancel(mPendingIntent);
-            mPendingIntent = null;
-        }
-    }
-
-    @Override
-    public void startRecording(Context context) {
-        Intent intent = new Intent(context, PerfAlarmReceiver.class);
-        intent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM);
-        mPendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
-        mAlarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, -1 /* now */,
-            POLLING_PERIOD_MILLIS, mPendingIntent);
-
-        for (PerfDataRecorder recorder : mRecorders) {
-            recorder.startRecording(context);
-        }
-    }
-
-    @Override
-    public void onAlarm(Context context) {
-        for (PerfDataRecorder recorder : mRecorders) {
-            recorder.onAlarm(context);
-        }
-    }
-
-    @Override
-    public void stopRecording(Context context) {
-        if (mPendingIntent != null) {
-            mAlarmMgr.cancel(mPendingIntent);
-            mPendingIntent = null;
-        }
-
-        for (PerfDataRecorder recorder : mRecorders) {
-            recorder.stopRecording(context);
-        }
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java
deleted file mode 100644
index 8613ac1..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java
+++ /dev/null
@@ -1,174 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Environment;
-import android.util.Log;
-import android.os.Debug;
-
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public abstract class PerfDataRecorder {
-    private static final String TAG = "loadtest.PerfDataRecorder";
-
-    protected final String mTimeAsString;
-    protected final String mColumnSuffix;
-
-    protected PerfDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
-        int burst, boolean includeCountMetric, boolean includeDurationMetric,
-        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
-        mTimeAsString = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date());
-        mColumnSuffix = getColumnSuffix(placebo, replication, bucket, periodSecs, burst,
-            includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
-            includeGaugeMetric);
-    }
-
-    /** Starts recording performance data. */
-    public abstract void startRecording(Context context);
-
-    /** Called periodically. For the recorder to sample data, if needed. */
-    public abstract void onAlarm(Context context);
-
-    /** Stops recording performance data, and writes it to disk. */
-    public abstract void stopRecording(Context context);
-
-    /** Runs the dumpsys command. */
-    protected void runDumpsysStats(Context context, String dumpFilename, String cmd,
-        String... args) {
-        boolean success = false;
-        // Call dumpsys Dump statistics to a file.
-        FileOutputStream fo = null;
-        try {
-            fo = context.openFileOutput(dumpFilename, Context.MODE_PRIVATE);
-            if (!Debug.dumpService(cmd, fo.getFD(), args)) {
-                Log.w(TAG, "Dumpsys failed.");
-            }
-            success = true;
-        } catch (IOException | SecurityException | NullPointerException e) {
-            // SecurityException may occur when trying to dump multi-user info.
-            // NPE can occur during dumpService  (root cause unknown).
-            throw new RuntimeException(e);
-        } finally {
-            closeQuietly(fo);
-        }
-    }
-
-    /**
-     * Reads a text file and parses each line, one by one. The result of the parsing is stored
-     * in the passed {@link StringBuffer}.
-     */
-    protected void readDumpData(Context context, String dumpFilename, PerfParser parser,
-        StringBuilder sb) {
-        FileInputStream fi = null;
-        BufferedReader br = null;
-        try {
-            fi = context.openFileInput(dumpFilename);
-            br = new BufferedReader(new InputStreamReader(fi));
-            String line = br.readLine();
-            while (line != null) {
-                String recordLine = parser.parseLine(line);
-                if (recordLine != null) {
-                  sb.append(recordLine).append('\n');
-                }
-                line = br.readLine();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        } finally {
-            closeQuietly(br);
-        }
-    }
-
-    /** Writes CSV data to a file. */
-    protected void writeData(Context context, String filePrefix, String columnPrefix,
-        StringBuilder sb) {
-        File dataFile = new File(getStorageDir(), filePrefix + mTimeAsString + ".csv");
-
-        FileWriter writer = null;
-        try {
-            writer = new FileWriter(dataFile);
-            writer.append(columnPrefix + mColumnSuffix + "\n");
-            writer.append(sb.toString());
-            writer.flush();
-            Log.d(TAG, "Finished writing data at " + dataFile.getAbsolutePath());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        } finally {
-            closeQuietly(writer);
-        }
-    }
-
-    /** Gets the suffix to use in the column name for perf data. */
-    private String getColumnSuffix(boolean placebo, int replication, TimeUnit bucket,
-        long periodSecs, int burst, boolean includeCountMetric, boolean includeDurationMetric,
-        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
-        if (placebo) {
-            return "_placebo_p=" + periodSecs;
-        }
-        StringBuilder sb = new StringBuilder()
-            .append("_r=" + replication)
-            .append("_bkt=" + bucket)
-            .append("_p=" + periodSecs)
-            .append("_bst=" + burst)
-            .append("_m=");
-        if (includeCountMetric) {
-            sb.append("c");
-        }
-        if (includeEventMetric) {
-            sb.append("e");
-        }
-        if (includeDurationMetric) {
-            sb.append("d");
-        }
-        if (includeGaugeMetric) {
-            sb.append("g");
-        }
-        if (includeValueMetric) {
-            sb.append("v");
-        }
-        return sb.toString();
-    }
-
-    private File getStorageDir() {
-        File file = new File(Environment.getExternalStoragePublicDirectory(
-            Environment.DIRECTORY_DOCUMENTS), "loadtest/" + mTimeAsString);
-        if (!file.mkdirs()) {
-            Log.e(TAG, "Directory not created");
-        }
-        return file;
-    }
-
-    private void closeQuietly(@Nullable Closeable c) {
-        if (c != null) {
-            try {
-                c.close();
-            } catch (IOException ignore) {
-            }
-        }
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java
deleted file mode 100644
index e000918..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java
+++ /dev/null
@@ -1,27 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.annotation.Nullable;
-
-public interface PerfParser {
-
-  /**
-   * Parses one line of the dumpsys output, and returns a string to write to the data file,
-   * or null if no string should be written.
-   */
-  @Nullable String parseLine(String line);
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java
deleted file mode 100644
index 5dcce9a..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java
+++ /dev/null
@@ -1,165 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.util.Log;
-import android.util.StatsLog;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages the pushing of atoms into logd for loadtesting.
- * We rely on small number of pushed atoms, and a config with metrics based on those atoms.
- * The atoms are:
- * <ul>
- *   <li> BatteryLevelChanged   - For EventMetric, CountMetric and GaugeMetric (no dimensions).
- *   <li> BleScanResultReceived - For CountMetric and ValueMetric, sliced by uid.
- *   <li> ChargingStateChanged  - For DurationMetric (no dimension).
- *   <li> GpsScanStateChanged   - For DurationMetric, sliced by uid.
- *   <li> ScreenStateChanged    - For Conditions with no dimensions.
- *   <li> AudioStateChanged     - For Conditions with dimensions (uid).
- * </ul>
- * The sequence is played over and over at a given frequency.
- */
-public class SequencePusher {
-    private static final String TAG = "SequencePusher";
-
-    /** Some atoms are pushed in burst of {@code mBurst} events. */
-    private final int mBurst;
-
-    /** If this is true, we don't log anything in logd. */
-    private final boolean mPlacebo;
-
-    /** Current state in the automaton. */
-    private int mCursor = 0;
-
-  public SequencePusher(int burst, boolean placebo) {
-        mBurst = burst;
-        mPlacebo = placebo;
-    }
-
-    /**
-     * Pushes the next atom to logd.
-     * This follows a small automaton which makes the right events and conditions overlap:
-     *   (0)  Push a burst of BatteryLevelChanged atoms.
-     *   (1)  Push a burst of BleScanResultReceived atoms.
-     *   (2)  Push ChargingStateChanged with BATTERY_STATUS_CHARGING once.
-     *   (3)  Push a burst of GpsScanStateChanged atoms with ON, with a different uid each time.
-     *   (4)  Push ChargingStateChanged with BATTERY_STATUS_NOT_CHARGING once.
-     *   (5)  Push a burst GpsScanStateChanged atoms with OFF, with a different uid each time.
-     *   (6)  Push ScreenStateChanged with STATE_ON once.
-     *   (7)  Push a burst of AudioStateChanged with ON, with a different uid each time.
-     *   (8)  Repeat steps (0)-(5).
-     *   (9)  Push ScreenStateChanged with STATE_OFF once.
-     *   (10) Push a burst of AudioStateChanged with OFF, with a different uid each time.
-     * and repeat.
-     */
-    public void next() {
-        Log.d(TAG, "Next step: " + mCursor);
-        if (mPlacebo) {
-            return;
-        }
-        switch (mCursor) {
-            case 0:
-            case 8:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, 50 + i /* battery_level */);
-                }
-                break;
-            case 1:
-            case 9:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, i /* uid */,
-                        100 /* num_of_results */);
-                }
-                break;
-            case 2:
-            case 10:
-                StatsLog.write(StatsLog.CHARGING_STATE_CHANGED,
-                    StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_CHARGING
-                    /* charging_state */);
-                break;
-            case 3:
-            case 11:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */,
-                        StatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON /* state */);
-                }
-                break;
-            case 4:
-            case 12:
-                StatsLog.write(StatsLog.CHARGING_STATE_CHANGED,
-                    StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING
-                    /* charging_state */);
-                break;
-            case 5:
-            case 13:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */,
-                        StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF /* state */);
-                }
-                break;
-            case 6:
-                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
-                    StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON /* display_state */);
-                break;
-            case 7:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */,
-                        StatsLog.AUDIO_STATE_CHANGED__STATE__ON /* state */);
-                }
-                break;
-            case 14:
-                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
-                    StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */);
-                break;
-            case 15:
-                for (int i = 0; i < mBurst; i++) {
-                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */,
-                        StatsLog.AUDIO_STATE_CHANGED__STATE__OFF /* state */);
-                }
-                break;
-            default:
-        }
-        mCursor++;
-        if (mCursor > 15) {
-            mCursor = 0;
-        }
-    }
-
-    /**
-     * Properly finishes in order to be close all conditions and durations.
-     */
-    public void finish() {
-        // Screen goes back to off. This will ensure that conditions get back to false.
-        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
-            StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */);
-        for (int i = 0; i < mBurst; i++) {
-          StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */,
-              StatsLog.AUDIO_STATE_CHANGED__STATE__OFF /* state */);
-        }
-        // Stop charging, to ensure the corresponding durations are closed.
-        StatsLog.write(StatsLog.CHARGING_STATE_CHANGED,
-            StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING
-            /* charging_state */);
-        // Stop scanning GPS, to ensure the corresponding conditions get back to false.
-        for (int i = 0; i < mBurst; i++) {
-          StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */,
-              StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF /* state */);
-        }
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
deleted file mode 100644
index 3939e7e..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
+++ /dev/null
@@ -1,62 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.content.Context;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-
-public class StatsdStatsRecorder extends PerfDataRecorder {
-    private static final String TAG = "loadtest.StatsdStatsRecorder";
-
-    private final LoadtestActivity mLoadtestActivity;
-
-    public StatsdStatsRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        TimeUnit bucket, long periodSecs, int burst, boolean includeCountMetric,
-        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
-        boolean includeGaugeMetric) {
-      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
-          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
-        mLoadtestActivity = loadtestActivity;
-    }
-
-    @Override
-    public void startRecording(Context context) {
-        // Nothing to do.
-    }
-
-    @Override
-    public void onAlarm(Context context) {
-        // Nothing to do.
-    }
-
-    @Override
-    public void stopRecording(Context context) {
-        StatsdStatsReport metadata = mLoadtestActivity.getMetadata();
-        if (metadata != null) {
-            int numConfigs = metadata.getConfigStatsCount();
-            StringBuilder sb = new StringBuilder();
-            StatsdStatsReport.ConfigStats configStats = metadata.getConfigStats(numConfigs - 1);
-            sb.append("metric_count,")
-                .append(configStats.getMetricCount() + "\n")
-                .append("condition_count,")
-                .append(configStats.getConditionCount() + "\n")
-                .append("matcher_count,")
-                .append(configStats.getMatcherCount() + "\n");
-            writeData(context, "statsdstats_", "stat,value", sb);
-        }
-    }
-}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
deleted file mode 100644
index d9f0ca9..0000000
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
+++ /dev/null
@@ -1,93 +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.
- */
-package com.android.statsd.loadtest;
-
-import android.content.Context;
-import android.util.Log;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Checks the correctness of the stats.
- */
-public class ValidationRecorder extends PerfDataRecorder {
-    private static final String TAG = "loadtest.ValidationRecorder";
-
-    private final LoadtestActivity mLoadtestActivity;
-
-    public ValidationRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        TimeUnit bucket, long periodSecs, int burst,  boolean includeCountMetric,
-        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
-        boolean includeGaugeMetric) {
-      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
-          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
-        mLoadtestActivity = loadtestActivity;
-    }
-
-    @Override
-    public void startRecording(Context context) {
-        // Nothing to do.
-    }
-
-    @Override
-    public void onAlarm(Context context) {
-        validateData();
-    }
-
-    @Override
-    public void stopRecording(Context context) {
-        validateData();
-    }
-
-    private void validateData() {
-        // The code below is commented out because it calls getData, which has the side-effect
-        // of clearing statsd's data buffer.
-        /*
-        List<ConfigMetricsReport> reports = mLoadtestActivity.getData();
-        if (reports != null) {
-            Log.d(TAG, "GOT DATA");
-            for (ConfigMetricsReport report : reports) {
-                for (StatsLogReport logReport : report.getMetricsList()) {
-                    if (!logReport.hasMetricId()) {
-                        Log.e(TAG, "Metric missing name.");
-                    }
-                }
-            }
-        }
-        */
-    }
-
-    private void validateEventBatteryLevelChanges(StatsLogReport logReport) {
-        Log.d(TAG, "Validating " + logReport.getMetricId());
-        if (logReport.hasEventMetrics()) {
-            Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount());
-            for (EventMetricData data : logReport.getEventMetrics().getDataList()) {
-                Log.d(TAG, "  Event : " + data.getAtom());
-            }
-        } else {
-            Log.d(TAG, "Metric is invalid");
-        }
-    }
-
-    private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) {
-        Log.d(TAG, "Validating " + logReport.getMetricId());
-    }
-}
diff --git a/cmds/statsd/tools/localtools/Android.bp b/cmds/statsd/tools/localtools/Android.bp
index 75a57a3..69a43a8 100644
--- a/cmds/statsd/tools/localtools/Android.bp
+++ b/cmds/statsd/tools/localtools/Android.bp
@@ -11,9 +11,8 @@
     ],
 }
 
-java_binary_host {
-    name: "statsd_testdrive",
-    manifest: "testdrive_manifest.txt",
+java_library_host {
+    name: "statsd_testdrive_lib",
     srcs: [
         "src/com/android/statsd/shelltools/testdrive/*.java",
         "src/com/android/statsd/shelltools/Utils.java",
@@ -22,4 +21,26 @@
         "platformprotos",
         "guava",
     ],
-}
\ No newline at end of file
+}
+
+
+java_binary_host {
+    name: "statsd_testdrive",
+    manifest: "testdrive_manifest.txt",
+    static_libs: [
+        "statsd_testdrive_lib",
+    ],
+}
+
+java_test_host {
+    name: "statsd_testdrive_test",
+    test_suites: ["general-tests"],
+    srcs: ["test/com/android/statsd/shelltools/testdrive/*.java"],
+    static_libs: [
+        "statsd_testdrive_lib",
+        "junit",
+        "platformprotos",
+        "guava",
+    ],
+}
+
diff --git a/cmds/statsd/tools/localtools/TEST_MAPPING b/cmds/statsd/tools/localtools/TEST_MAPPING
new file mode 100644
index 0000000..7c8a3db
--- /dev/null
+++ b/cmds/statsd/tools/localtools/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "statsd_testdrive_test",
+      "host": true
+    }
+  ]
+}
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index a381f9c..6a74480 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -17,16 +17,23 @@
 
 import com.android.os.StatsLog.ConfigMetricsReportList;
 
+import com.google.common.io.Files;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Utilities for local use of statsd.
@@ -80,7 +87,8 @@
      * @throws InterruptedException
      */
     public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
-            boolean useShellUid, Logger logger) throws IOException, InterruptedException {
+            boolean useShellUid, Logger logger, String deviceSerial)
+            throws IOException, InterruptedException {
         try {
             File outputFile = File.createTempFile("statsdret", ".bin");
             outputFile.deleteOnExit();
@@ -88,6 +96,8 @@
                     outputFile,
                     logger,
                     "adb",
+                    "-s",
+                    deviceSerial,
                     "shell",
                     CMD_DUMP_REPORT,
                     useShellUid ? SHELL_UID : "",
@@ -117,12 +127,14 @@
      * @throws IOException
      * @throws InterruptedException
      */
-    public static void logAppBreadcrumb(int label, int state, Logger logger)
+    public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial)
             throws IOException, InterruptedException {
         runCommand(
                 null,
                 logger,
                 "adb",
+                "-s",
+                deviceSerial,
                 "shell",
                 CMD_LOG_APP_BREADCRUMB,
                 String.valueOf(label),
@@ -145,13 +157,14 @@
      * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName))
      * If all else fails, assume it will work (letting future commands deal with any errors).
      */
-    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) {
+    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename,
+            String deviceSerial) {
         BufferedReader in = null;
         try {
             File outFileSdk = File.createTempFile("shelltools_sdk", "tmp");
             outFileSdk.deleteOnExit();
             runCommand(outFileSdk, logger,
-                    "adb", "shell", "getprop", "ro.build.version.sdk");
+                    "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk");
             in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk)));
             // If NullPointerException/NumberFormatException/etc., just catch and return true.
             int sdk = Integer.parseInt(in.readLine().trim());
@@ -162,7 +175,7 @@
                 File outFileCode = File.createTempFile("shelltools_codename", "tmp");
                 outFileCode.deleteOnExit();
                 runCommand(outFileCode, logger,
-                        "adb", "shell", "getprop", "ro.build.version.codename");
+                        "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename");
                 in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode)));
                 return in.readLine().startsWith(minCodename);
             } else {
@@ -190,4 +203,82 @@
             return record.getMessage() + "\n";
         }
     }
+
+    /**
+     * Parse the result of "adb devices" to return the list of connected devices.
+     * @param logger Logger to log error messages
+     * @return List of the serial numbers of the connected devices.
+     */
+    public static List<String> getDeviceSerials(Logger logger) {
+        try {
+            ArrayList<String> devices = new ArrayList<>();
+            File outFile = File.createTempFile("device_serial", "tmp");
+            outFile.deleteOnExit();
+            Utils.runCommand(outFile, logger, "adb", "devices");
+            List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset());
+            Pattern regex = Pattern.compile("^(.*)\tdevice$");
+            for (String line : outputLines) {
+                Matcher m = regex.matcher(line);
+                if (m.find()) {
+                    devices.add(m.group(1));
+                }
+            }
+            return devices;
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Returns ANDROID_SERIAL environment variable, or null if that is undefined or unavailable.
+     * @param logger Destination of error messages.
+     * @return String value of ANDROID_SERIAL environment variable, or null.
+     */
+    public static String getDefaultDevice(Logger logger) {
+        try {
+            return System.getenv("ANDROID_SERIAL");
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "Failed to check ANDROID_SERIAL environment variable.",
+                    ex);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the device to use if one can be deduced, or null.
+     * @param device Command-line specified device, or null.
+     * @param connectedDevices List of all connected devices.
+     * @param defaultDevice Environment-variable specified device, or null.
+     * @param logger Destination of error messages.
+     * @return Device to use, or null.
+     */
+    public static String chooseDevice(String device, List<String> connectedDevices,
+            String defaultDevice, Logger logger) {
+        if (connectedDevices == null || connectedDevices.isEmpty()) {
+            logger.severe("No connected device.");
+            return null;
+        }
+        if (device != null) {
+            if (connectedDevices.contains(device)) {
+                return device;
+            }
+            logger.severe("Device not connected: " + device);
+            return null;
+        }
+        if (connectedDevices.size() == 1) {
+            return connectedDevices.get(0);
+        }
+        if (defaultDevice != null) {
+            if (connectedDevices.contains(defaultDevice)) {
+                return defaultDevice;
+            } else {
+                logger.severe("ANDROID_SERIAL device is not connected: " + defaultDevice);
+                return null;
+            }
+        }
+        logger.severe("More than one device is connected. Choose one"
+                + " with -s DEVICE_SERIAL or environment variable ANDROID_SERIAL.");
+        return null;
+    }
 }
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
index 2eb4660..ec3c7df 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -26,6 +26,7 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.List;
 import java.util.logging.Logger;
 
 /**
@@ -49,7 +50,7 @@
     public static final String HELP_STRING =
         "Usage:\n\n" +
 
-        "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Uploads the given statsd config file (in binary or human-readable-text format).\n" +
         "  If a config with this id already exists, removes it first.\n" +
         "    CONFIG_FILE    Location of config file on host.\n" +
@@ -59,12 +60,12 @@
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Same as upload, but does not remove the old config first (if it already exists).\n" +
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
         "  Prints the output statslog data (in binary or human-readable-text format).\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         "    --binary       Output should be in binary, instead of default human-readable text.\n" +
@@ -75,13 +76,13 @@
         //                                                      --include_current_bucket --proto
         "\n" +
 
-        "statsd_localdrive remove [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" +
         "  Removes the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive clear [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" +
         "  Clears the data associated with the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
@@ -94,29 +95,51 @@
     /** Usage: make statsd_localdrive && statsd_localdrive */
     public static void main(String[] args) {
         Utils.setUpLogger(sLogger, DEBUG);
+        if (args.length == 0) {
+            printHelp();
+            return;
+        }
 
-        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) {
+        int remainingArgsLength = args.length;
+        String deviceSerial = null;
+        if (args[0].equals("-s")) {
+            if (args.length == 1) {
+                printHelp();
+            }
+            deviceSerial = args[1];
+            remainingArgsLength -= 2;
+        }
+
+        List<String> connectedDevices = Utils.getDeviceSerials(sLogger);
+        deviceSerial = Utils.chooseDevice(deviceSerial, connectedDevices,
+                Utils.getDefaultDevice(sLogger), sLogger);
+        if (deviceSerial == null) {
+            return;
+        }
+
+        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) {
             sLogger.severe("LocalDrive only works with statsd versions for Android "
                     + MIN_CODENAME + " or higher.");
             return;
         }
 
-        if (args.length > 0) {
-            switch (args[0]) {
+        int idx = args.length - remainingArgsLength;
+        if (remainingArgsLength > 0) {
+            switch (args[idx]) {
                 case "clear":
-                    cmdClear(args);
+                    cmdClear(args, idx, deviceSerial);
                     return;
                 case "get-data":
-                    cmdGetData(args);
+                    cmdGetData(args, idx, deviceSerial);
                     return;
                 case "remove":
-                    cmdRemove(args);
+                    cmdRemove(args, idx);
                     return;
                 case "update":
-                    cmdUpdate(args);
+                    cmdUpdate(args, idx, deviceSerial);
                     return;
                 case "upload":
-                    cmdUpload(args);
+                    cmdUpload(args, idx, deviceSerial);
                     return;
             }
         }
@@ -128,17 +151,18 @@
     }
 
     // upload CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpload(String[] args) {
-        return updateConfig(args, true);
+    private static boolean cmdUpload(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, true, deviceSerial);
     }
 
     // update CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpdate(String[] args) {
-        return updateConfig(args, false);
+    private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, false, deviceSerial);
     }
 
-    private static boolean updateConfig(String[] args, boolean removeOldConfig) {
-        int argCount = args.length - 1; // Used up one for upload/update.
+    private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig,
+            String deviceSerial) {
+        int argCount = args.length - 1 - idx; // Used up one for upload/update.
 
         // Get CONFIG_FILE
         if (argCount < 1) {
@@ -146,7 +170,7 @@
             printHelp();
             return false;
         }
-        final String origConfigLocation = args[1];
+        final String origConfigLocation = args[idx + 1];
         if (!new File(origConfigLocation).exists()) {
             sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
             return false;
@@ -154,13 +178,13 @@
         argCount--;
 
         // Get --binary
-        boolean binary = contains(args, 2, BINARY_FLAG);
+        boolean binary = contains(args, idx + 2, BINARY_FLAG);
         if (binary) argCount --;
 
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 2);
+            configId = getConfigId(argCount < 1, args, idx + 2);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -174,7 +198,8 @@
             try {
                 Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
                         Utils.SHELL_UID, String.valueOf(configId));
-                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                        deviceSerial);
             } catch (InterruptedException | IOException e) {
                 sLogger.severe("Failed to remove config: " + e.getMessage());
                 return false;
@@ -218,19 +243,19 @@
     }
 
     // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
-    private static boolean cmdGetData(String[] args) {
-        boolean binary = contains(args, 1, BINARY_FLAG);
-        boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
-        boolean clearData = contains(args, 1, CLEAR_DATA);
+    private static boolean cmdGetData(String[] args, int idx, String deviceSerial) {
+        boolean binary = contains(args, idx + 1, BINARY_FLAG);
+        boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG);
+        boolean clearData = contains(args, idx + 1, CLEAR_DATA);
 
         // Get CONFIG_ID
-        int argCount = args.length - 1; // Used up one for get-data.
+        int argCount = args.length - 1 - idx; // Used up one for get-data.
         if (binary) argCount--;
         if (noUidMap) argCount--;
         if (clearData) argCount--;
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 1);
+            configId = getConfigId(argCount < 1, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -243,7 +268,8 @@
         // Even if the args request no modifications, we still parse it to make sure it's valid.
         ConfigMetricsReportList reportList;
         try {
-            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger);
+            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -274,11 +300,11 @@
     }
 
     // clear [CONFIG_ID]
-    private static boolean cmdClear(String[] args) {
+    private static boolean cmdClear(String[] args, int idx, String deviceSerial) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -287,7 +313,8 @@
         sLogger.fine(String.format("cmdClear with %d", configId));
 
         try {
-            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -296,11 +323,11 @@
     }
 
     // remove [CONFIG_ID]
-    private static boolean cmdRemove(String[] args) {
+    private static boolean cmdRemove(String[] args, int idx) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index c97f035..51bcad1 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -15,27 +15,34 @@
  */
 package com.android.statsd.shelltools.testdrive;
 
+import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventMetric;
 import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.PullAtomPackages;
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.statsd.shelltools.Utils;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.io.Files;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -49,190 +56,364 @@
     private static final int VENDOR_PULLED_ATOM_START_TAG = 150000;
     private static final long CONFIG_ID = 54321;
     private static final String[] ALLOWED_LOG_SOURCES = {
-        "AID_GRAPHICS",
-        "AID_INCIDENTD",
-        "AID_STATSD",
-        "AID_RADIO",
-        "com.android.systemui",
-        "com.android.vending",
-        "AID_SYSTEM",
-        "AID_ROOT",
-        "AID_BLUETOOTH",
-        "AID_LMKD",
-        "com.android.managedprovisioning",
-        "AID_MEDIA",
-        "AID_NETWORK_STACK"
+            "AID_GRAPHICS",
+            "AID_INCIDENTD",
+            "AID_STATSD",
+            "AID_RADIO",
+            "com.android.systemui",
+            "com.android.vending",
+            "AID_SYSTEM",
+            "AID_ROOT",
+            "AID_BLUETOOTH",
+            "AID_LMKD",
+            "com.android.managedprovisioning",
+            "AID_MEDIA",
+            "AID_NETWORK_STACK",
+            "com.google.android.providers.media.module",
+    };
+    private static final String[] DEFAULT_PULL_SOURCES = {
+            "AID_SYSTEM",
+            "AID_RADIO"
     };
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
-    private String mAdditionalAllowedPackage;
-    private final Set<Long> mTrackedMetrics = new HashSet<>();
+    @VisibleForTesting
+    String mDeviceSerial = null;
+    @VisibleForTesting
+    Dumper mDumper = new BasicDumper();
 
     public static void main(String[] args) {
-        TestDrive testDrive = new TestDrive();
-        Set<Integer> trackedAtoms = new HashSet<>();
+        final Configuration configuration = new Configuration();
+        final TestDrive testDrive = new TestDrive();
         Utils.setUpLogger(LOGGER, false);
-        String remoteConfigPath = null;
 
-        if (args.length < 1) {
-            LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] "
-                    + "<atomId1> <atomId2> ... <atomIdN>");
+        if (!testDrive.processArgs(configuration, args,
+                Utils.getDeviceSerials(LOGGER), Utils.getDefaultDevice(LOGGER))) {
             return;
         }
 
-        if (args.length >= 3 && args[0].equals("-p")) {
-            testDrive.mAdditionalAllowedPackage = args[1];
+        final ConfigMetricsReportList reports = testDrive.testDriveAndGetReports(
+                configuration.createConfig(), configuration.hasPulledAtoms(),
+                configuration.hasPushedAtoms());
+        if (reports != null) {
+            configuration.dumpMetrics(reports, testDrive.mDumper);
+        }
+    }
+
+    boolean processArgs(Configuration configuration, String[] args, List<String> connectedDevices,
+            String defaultDevice) {
+        if (args.length < 1) {
+            LOGGER.severe("Usage: ./test_drive [-one] "
+                    + "[-p additional_allowed_package] "
+                    + "[-s DEVICE_SERIAL_NUMBER] "
+                    + "<atomId1> <atomId2> ... <atomIdN>");
+            return false;
         }
 
-        for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) {
+        int first_arg = 0;
+        // Consume all flags, which must precede all atoms
+        for (; first_arg < args.length; ++first_arg) {
+            String arg = args[first_arg];
+            int remaining_args = args.length - first_arg;
+            if (remaining_args >= 2 && arg.equals("-one")) {
+                LOGGER.info("Creating one event metric to catch all pushed atoms.");
+                configuration.mOnePushedAtomEvent = true;
+            } else if (remaining_args >= 2 && arg.equals("-terse")) {
+                LOGGER.info("Terse output format.");
+                mDumper = new TerseDumper();
+            } else if (remaining_args >= 3 && arg.equals("-p")) {
+                configuration.mAdditionalAllowedPackage = args[++first_arg];
+            } else if (remaining_args >= 3 && arg.equals("-s")) {
+                mDeviceSerial = args[++first_arg];
+            } else {
+                break;  // Found the atom list
+            }
+        }
+
+        mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER);
+        if (mDeviceSerial == null) {
+            return false;
+        }
+
+        for ( ; first_arg < args.length; ++first_arg) {
+            String atom = args[first_arg];
             try {
-                int atomId = Integer.valueOf(args[i]);
-                if (Atom.getDescriptor().findFieldByNumber(atomId) == null) {
-                    LOGGER.log(Level.SEVERE, "No such atom found: " + args[i]);
-                    continue;
-                }
-                trackedAtoms.add(atomId);
+                configuration.addAtom(Integer.valueOf(atom));
             } catch (NumberFormatException e) {
-                LOGGER.log(Level.SEVERE, "Bad atom id provided: " + args[i]);
-                continue;
+                LOGGER.severe("Bad atom id provided: " + atom);
             }
         }
 
+        return configuration.hasPulledAtoms() || configuration.hasPushedAtoms();
+    }
+
+    private ConfigMetricsReportList testDriveAndGetReports(StatsdConfig config,
+            boolean hasPulledAtoms, boolean hasPushedAtoms) {
+        if (config == null) {
+            LOGGER.severe("Failed to create valid config.");
+            return null;
+        }
+
+        String remoteConfigPath = null;
         try {
-            StatsdConfig config = testDrive.createConfig(trackedAtoms);
-            if (config == null) {
-                LOGGER.log(Level.SEVERE, "Failed to create valid config.");
-                return;
-            }
-            remoteConfigPath = testDrive.pushConfig(config);
-            LOGGER.info("Pushed the following config to statsd:");
+            remoteConfigPath = pushConfig(config, mDeviceSerial);
+            LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial
+                    + "':");
             LOGGER.info(config.toString());
-            if (!hasPulledAtom(trackedAtoms)) {
+            if (hasPushedAtoms) {
+                LOGGER.info("Now please play with the device to trigger the event.");
+            }
+            if (!hasPulledAtoms) {
                 LOGGER.info(
-                        "Now please play with the device to trigger the event. All events should "
-                                + "be dumped after 1 min ...");
+                        "All events should be dumped after 1 min ...");
                 Thread.sleep(60_000);
             } else {
-                LOGGER.info("Now wait for 1.5 minutes ...");
+                LOGGER.info("All events should be dumped after 1.5 minutes ...");
                 Thread.sleep(15_000);
-                Utils.logAppBreadcrumb(0, 0, LOGGER);
+                Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial);
                 Thread.sleep(75_000);
             }
-            testDrive.dumpMetrics();
+            return Utils.getReportList(CONFIG_ID, true, false, LOGGER,
+                    mDeviceSerial);
         } catch (Exception e) {
             LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e);
         } finally {
-            testDrive.removeConfig();
+            removeConfig(mDeviceSerial);
             if (remoteConfigPath != null) {
                 try {
-                    Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath);
+                    Utils.runCommand(null, LOGGER,
+                            "adb", "-s", mDeviceSerial, "shell", "rm",
+                            remoteConfigPath);
                 } catch (Exception e) {
                     LOGGER.log(Level.WARNING,
                             "Unable to remove remote config file: " + remoteConfigPath, e);
                 }
             }
         }
+        return null;
     }
 
-    private void dumpMetrics() throws Exception {
-        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER);
-        // We may get multiple reports. Take the last one.
-        ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
-        for (StatsLogReport statsLog : report.getMetricsList()) {
-            if (mTrackedMetrics.contains(statsLog.getMetricId())) {
-                LOGGER.info(statsLog.toString());
+    static class Configuration {
+        boolean mOnePushedAtomEvent = false;
+        @VisibleForTesting
+        Set<Integer> mPushedAtoms = new TreeSet<>();
+        @VisibleForTesting
+        Set<Integer> mPulledAtoms = new TreeSet<>();
+        @VisibleForTesting
+        String mAdditionalAllowedPackage = null;
+        private final Set<Long> mTrackedMetrics = new HashSet<>();
+
+        private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) {
+            // We may get multiple reports. Take the last one.
+            ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
+            for (StatsLogReport statsLog : report.getMetricsList()) {
+                if (isTrackedMetric(statsLog.getMetricId())) {
+                    dumper.dump(statsLog);
+                }
             }
         }
-    }
 
-    private StatsdConfig createConfig(Set<Integer> atomIds) {
-        long metricId = METRIC_ID_BASE;
-        long atomMatcherId = ATOM_MATCHER_ID_BASE;
-
-        ArrayList<String> allowedSources = new ArrayList<>();
-        Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES);
-        if (mAdditionalAllowedPackage != null) {
-            allowedSources.add(mAdditionalAllowedPackage);
+        boolean isTrackedMetric(long metricId) {
+            return mTrackedMetrics.contains(metricId);
         }
 
-        StatsdConfig.Builder builder = StatsdConfig.newBuilder();
-        builder
-            .addAllAllowedLogSource(allowedSources)
-            .setHashStringsInMetricReport(false);
-
-        if (hasPulledAtom(atomIds)) {
-            builder.addAtomMatcher(
-                    createAtomMatcher(
-                            Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, APP_BREADCRUMB_MATCHER_ID));
+        static boolean isPulledAtom(int atomId) {
+            return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG
+                    || atomId >= VENDOR_PULLED_ATOM_START_TAG;
         }
 
-        for (int atomId : atomIds) {
-            if (isPulledAtom(atomId)) {
+        void addAtom(Integer atom) {
+            if (Atom.getDescriptor().findFieldByNumber(atom) == null) {
+                LOGGER.severe("No such atom found: " + atom);
+                return;
+            }
+            if (isPulledAtom(atom)) {
+                mPulledAtoms.add(atom);
+            } else {
+                mPushedAtoms.add(atom);
+            }
+        }
+
+        private boolean hasPulledAtoms() {
+            return !mPulledAtoms.isEmpty();
+        }
+
+        private boolean hasPushedAtoms() {
+            return !mPushedAtoms.isEmpty();
+        }
+
+        StatsdConfig createConfig() {
+            long metricId = METRIC_ID_BASE;
+            long atomMatcherId = ATOM_MATCHER_ID_BASE;
+
+            StatsdConfig.Builder builder = baseBuilder();
+
+            if (hasPulledAtoms()) {
+                builder.addAtomMatcher(
+                        createAtomMatcher(
+                                Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
+                                APP_BREADCRUMB_MATCHER_ID));
+            }
+
+            for (int atomId : mPulledAtoms) {
                 builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId));
                 GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder();
                 gaugeMetricBuilder
-                    .setId(metricId)
-                    .setWhat(atomMatcherId)
-                    .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID)
-                    .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
-                    .setMaxNumGaugeAtomsPerBucket(100);
+                        .setId(metricId)
+                        .setWhat(atomMatcherId)
+                        .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID)
+                        .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                        .setBucket(TimeUnit.ONE_MINUTE)
+                        .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
+                        .setMaxNumGaugeAtomsPerBucket(100);
                 builder.addGaugeMetric(gaugeMetricBuilder.build());
-            } else {
-                EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder();
-                eventMetricBuilder
-                    .setId(metricId)
-                    .setWhat(atomMatcherId);
-                builder.addEventMetric(eventMetricBuilder.build());
-                builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId));
+                atomMatcherId++;
+                mTrackedMetrics.add(metricId++);
             }
-            atomMatcherId++;
-            mTrackedMetrics.add(metricId++);
+
+            // A simple atom matcher for each pushed atom.
+            List<AtomMatcher> simpleAtomMatchers = new ArrayList<>();
+            for (int atomId : mPushedAtoms) {
+                final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++);
+                simpleAtomMatchers.add(atomMatcher);
+                builder.addAtomMatcher(atomMatcher);
+            }
+
+            if (mOnePushedAtomEvent) {
+                // Create a union event metric, using an matcher that matches all pulled atoms.
+                AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers,
+                        atomMatcherId);
+                builder.addAtomMatcher(unionAtomMatcher);
+                EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder();
+                eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId());
+                builder.addEventMetric(eventMetricBuilder.build());
+                mTrackedMetrics.add(metricId++);
+            } else {
+                // Create multiple event metrics, one per pulled atom.
+                for (AtomMatcher atomMatcher : simpleAtomMatchers) {
+                    EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder();
+                    eventMetricBuilder
+                            .setId(metricId)
+                            .setWhat(atomMatcher.getId());
+                    builder.addEventMetric(eventMetricBuilder.build());
+                    mTrackedMetrics.add(metricId++);
+                }
+            }
+
+            return builder.build();
         }
-        return builder.build();
+
+        private static AtomMatcher createAtomMatcher(int atomId, long matcherId) {
+            AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder();
+            atomMatcherBuilder
+                    .setId(matcherId)
+                    .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId));
+            return atomMatcherBuilder.build();
+        }
+
+        private AtomMatcher createUnionMatcher(List<AtomMatcher> simpleAtomMatchers,
+                long atomMatcherId) {
+            AtomMatcher.Combination.Builder combinationBuilder =
+                    AtomMatcher.Combination.newBuilder();
+            combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR);
+            for (AtomMatcher matcher : simpleAtomMatchers) {
+                combinationBuilder.addMatcher(matcher.getId());
+            }
+            AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder();
+            atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build());
+            return atomMatcherBuilder.build();
+        }
+
+        private StatsdConfig.Builder baseBuilder() {
+            ArrayList<String> allowedSources = new ArrayList<>();
+            Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES);
+            if (mAdditionalAllowedPackage != null) {
+                allowedSources.add(mAdditionalAllowedPackage);
+            }
+            return StatsdConfig.newBuilder()
+                    .addAllAllowedLogSource(allowedSources)
+                    .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES))
+                    .addPullAtomPackages(PullAtomPackages.newBuilder()
+                            .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER)
+                            .addPackages("AID_GPU_SERVICE"))
+                    .addPullAtomPackages(PullAtomPackages.newBuilder()
+                            .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER)
+                            .addPackages("AID_GPU_SERVICE"))
+                    .addPullAtomPackages(PullAtomPackages.newBuilder()
+                            .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER)
+                            .addPackages("AID_STATSD"))
+                    .addPullAtomPackages(PullAtomPackages.newBuilder()
+                            .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER)
+                            .addPackages("com.google.android.providers.media.module"))
+                    .setHashStringsInMetricReport(false);
+        }
     }
 
-    private static AtomMatcher createAtomMatcher(int atomId, long matcherId) {
-        AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder();
-        atomMatcherBuilder
-                .setId(matcherId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId));
-        return atomMatcherBuilder.build();
+    interface Dumper {
+        void dump(StatsLogReport report);
     }
 
-    private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException {
+    static class BasicDumper implements Dumper {
+        @Override
+        public void dump(StatsLogReport report) {
+            System.out.println(report.toString());
+        }
+    }
+
+    static class TerseDumper extends BasicDumper {
+        @Override
+        public void dump(StatsLogReport report) {
+            if (report.hasGaugeMetrics()) {
+                dumpGaugeMetrics(report);
+            }
+            if (report.hasEventMetrics()) {
+                dumpEventMetrics(report);
+            }
+        }
+        void dumpEventMetrics(StatsLogReport report) {
+            final List<StatsLog.EventMetricData> data = report.getEventMetrics().getDataList();
+            if (data.isEmpty()) {
+                return;
+            }
+            long firstTimestampNanos = data.get(0).getElapsedTimestampNanos();
+            for (StatsLog.EventMetricData event : data) {
+                final double deltaSec = (event.getElapsedTimestampNanos() - firstTimestampNanos)
+                        / 1e9;
+                System.out.println(
+                        String.format("+%.3fs: %s", deltaSec, event.getAtom().toString()));
+            }
+        }
+        void dumpGaugeMetrics(StatsLogReport report) {
+            final List<StatsLog.GaugeMetricData> data = report.getGaugeMetrics().getDataList();
+            if (data.isEmpty()) {
+                return;
+            }
+            for (StatsLog.GaugeMetricData gauge : data) {
+                System.out.println(gauge.toString());
+            }
+        }
+    }
+
+    private static String pushConfig(StatsdConfig config, String deviceSerial)
+            throws IOException, InterruptedException {
         File configFile = File.createTempFile("statsdconfig", ".config");
         configFile.deleteOnExit();
         Files.write(config.toByteArray(), configFile);
         String remotePath = "/data/local/tmp/" + configFile.getName();
-        Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath);
-        Utils.runCommand(null, LOGGER,
-                "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "push", configFile.getAbsolutePath(), remotePath);
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
                 String.valueOf(CONFIG_ID));
         return remotePath;
     }
 
-    private static void removeConfig() {
+    private static void removeConfig(String deviceSerial) {
         try {
-            Utils.runCommand(null, LOGGER,
-                    "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
+            Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                    "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
         } catch (Exception e) {
-            LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage());
+            LOGGER.severe("Failed to remove config: " + e.getMessage());
         }
     }
-
-    private static boolean isPulledAtom(int atomId) {
-        return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG
-                || atomId >= VENDOR_PULLED_ATOM_START_TAG;
-    }
-
-    private static boolean hasPulledAtom(Set<Integer> atoms) {
-        for (Integer i : atoms) {
-            if (isPulledAtom(i)) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java
new file mode 100644
index 0000000..b1cc60f
--- /dev/null
+++ b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.statsd.shelltools.testdrive;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link TestDrive}
+ */
+public class ConfigurationTest {
+
+    private StatsdConfigProto.AtomMatcher findAndRemoveAtomMatcherById(
+            List<StatsdConfigProto.AtomMatcher> atomMatchers, long id) {
+        int numMatches = 0;
+        StatsdConfigProto.AtomMatcher match = null;
+        for (StatsdConfigProto.AtomMatcher atomMatcher : atomMatchers) {
+            if (id == atomMatcher.getId()) {
+                ++numMatches;
+                match = atomMatcher;
+            }
+        }
+        if (numMatches == 1) {
+            atomMatchers.remove(match);
+            return match;
+        }
+        return null;  // Too many, or not found
+    }
+
+    private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration();
+
+    @Test
+    public void testOnePushed() {
+        final int atom = 90;
+        assertFalse(TestDrive.Configuration.isPulledAtom(atom));
+        mConfiguration.addAtom(atom);
+        StatsdConfig config = mConfiguration.createConfig();
+
+        //event_metric {
+        //  id: 1111
+        //  what: 1234567
+        //}
+        //atom_matcher {
+        //  id: 1234567
+        //  simple_atom_matcher {
+        //    atom_id: 90
+        //  }
+        //}
+
+        assertEquals(1, config.getEventMetricCount());
+        assertEquals(0, config.getGaugeMetricCount());
+
+        assertTrue(mConfiguration.isTrackedMetric(config.getEventMetric(0).getId()));
+
+        final List<StatsdConfigProto.AtomMatcher> atomMatchers =
+                new ArrayList<>(config.getAtomMatcherList());
+        assertEquals(atom,
+                findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat())
+                        .getSimpleAtomMatcher().getAtomId());
+        assertEquals(0, atomMatchers.size());
+    }
+
+    @Test
+    public void testOnePulled() {
+        final int atom = 10022;
+        assertTrue(TestDrive.Configuration.isPulledAtom(atom));
+        mConfiguration.addAtom(atom);
+        StatsdConfig config = mConfiguration.createConfig();
+
+        //gauge_metric {
+        //  id: 1111
+        //  what: 1234567
+        //  gauge_fields_filter {
+        //    include_all: true
+        //  }
+        //  bucket: ONE_MINUTE
+        //  sampling_type: FIRST_N_SAMPLES
+        //  max_num_gauge_atoms_per_bucket: 100
+        //  trigger_event: 1111111
+        //}
+        //atom_matcher {
+        //  id: 1111111
+        //  simple_atom_matcher {
+        //    atom_id: 47
+        //  }
+        //}
+        //atom_matcher {
+        //  id: 1234567
+        //  simple_atom_matcher {
+        //    atom_id: 10022
+        //  }
+        //}
+
+        assertEquals(0, config.getEventMetricCount());
+        assertEquals(1, config.getGaugeMetricCount());
+
+        assertTrue(mConfiguration.isTrackedMetric(config.getGaugeMetric(0).getId()));
+
+        final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0);
+        assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll());
+
+        final List<StatsdConfigProto.AtomMatcher> atomMatchers =
+                new ArrayList<>(config.getAtomMatcherList());
+        assertEquals(atom,
+                findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat())
+                        .getSimpleAtomMatcher().getAtomId());
+        assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
+                findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent())
+                        .getSimpleAtomMatcher().getAtomId());
+        assertEquals(0, atomMatchers.size());
+    }
+
+    @Test
+    public void testOnePulledTwoPushed() {
+        final int pulledAtom = 10022;
+        assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom));
+        mConfiguration.addAtom(pulledAtom);
+
+        Integer[] pushedAtoms = new Integer[]{244, 245};
+        for (int atom : pushedAtoms) {
+            assertFalse(TestDrive.Configuration.isPulledAtom(atom));
+            mConfiguration.addAtom(atom);
+        }
+        StatsdConfig config = mConfiguration.createConfig();
+
+        //  event_metric {
+        //    id: 1111
+        //    what: 1234567
+        //  }
+        //  event_metric {
+        //    id: 1112
+        //    what: 1234568
+        //  }
+        //  gauge_metric {
+        //    id: 1114
+        //    what: 1234570
+        //    gauge_fields_filter {
+        //      include_all: true
+        //    }
+        //    bucket: ONE_MINUTE
+        //    sampling_type: FIRST_N_SAMPLES
+        //    max_num_gauge_atoms_per_bucket: 100
+        //    trigger_event: 1111111
+        //  }
+        //  atom_matcher {
+        //    id: 1111111
+        //    simple_atom_matcher {
+        //      atom_id: 47
+        //    }
+        //  }
+        //  atom_matcher {
+        //    id: 1234567
+        //    simple_atom_matcher {
+        //      atom_id: 244
+        //    }
+        //  }
+        //  atom_matcher {
+        //    id: 1234568
+        //    simple_atom_matcher {
+        //      atom_id: 245
+        //    }
+        //  }
+        //  atom_matcher {
+        //    id: 1234570
+        //    simple_atom_matcher {
+        //      atom_id: 10022
+        //    }
+        //  }
+
+        assertEquals(2, config.getEventMetricCount());
+        assertEquals(1, config.getGaugeMetricCount());
+
+        final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0);
+        assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId()));
+        assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll());
+        for (StatsdConfigProto.EventMetric eventMetric : config.getEventMetricList()) {
+            assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId()));
+        }
+
+        final List<StatsdConfigProto.AtomMatcher> atomMatchers =
+                new ArrayList<>(config.getAtomMatcherList());
+
+        assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat())
+                .getSimpleAtomMatcher().getAtomId());
+        assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
+                findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent())
+                        .getSimpleAtomMatcher().getAtomId());
+
+        Integer[] actualAtoms = new Integer[]{
+                findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat())
+                        .getSimpleAtomMatcher().getAtomId(),
+                findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(1).getWhat())
+                        .getSimpleAtomMatcher().getAtomId()};
+        Arrays.sort(actualAtoms);
+        assertArrayEquals(pushedAtoms, actualAtoms);
+
+        assertEquals(0, atomMatchers.size());
+    }
+
+    @Test
+    public void testOnePulledTwoPushedTogether() {
+        mConfiguration.mOnePushedAtomEvent = true;  // Use one event grabbing all pushed atoms
+
+        final int pulledAtom = 10022;
+        assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom));
+        mConfiguration.addAtom(pulledAtom);
+
+        Integer[] pushedAtoms = new Integer[]{244, 245};
+        for (int atom : pushedAtoms) {
+            assertFalse(TestDrive.Configuration.isPulledAtom(atom));
+            mConfiguration.addAtom(atom);
+        }
+        StatsdConfig config = mConfiguration.createConfig();
+
+        //    event_metric {
+        //      id: 1112
+        //      what: 1234570
+        //    }
+        //    gauge_metric {
+        //      id: 1111
+        //      what: 1234567
+        //      gauge_fields_filter {
+        //        include_all: true
+        //      }
+        //      bucket: ONE_MINUTE
+        //      sampling_type: FIRST_N_SAMPLES
+        //      max_num_gauge_atoms_per_bucket: 100
+        //      trigger_event: 1111111
+        //    }
+        //    atom_matcher {
+        //      id: 1111111
+        //      simple_atom_matcher {
+        //        atom_id: 47
+        //      }
+        //    }
+        //    atom_matcher {
+        //      id: 1234567
+        //      simple_atom_matcher {
+        //        atom_id: 10022
+        //      }
+        //    }
+        //    atom_matcher {
+        //      id: 1234568
+        //      simple_atom_matcher {
+        //        atom_id: 244
+        //      }
+        //    }
+        //    atom_matcher {
+        //      id: 1234569
+        //      simple_atom_matcher {
+        //        atom_id: 245
+        //      }
+        //    }
+        //    atom_matcher {
+        //      id: 1234570
+        //      combination {
+        //        operation: OR
+        //        matcher: 1234568
+        //        matcher: 1234569
+        //      }
+        //    }
+
+        assertEquals(1, config.getEventMetricCount());
+        assertEquals(1, config.getGaugeMetricCount());
+
+        final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0);
+        assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId()));
+        assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll());
+
+        StatsdConfigProto.EventMetric eventMetric = config.getEventMetric(0);
+        assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId()));
+
+        final List<StatsdConfigProto.AtomMatcher> atomMatchers =
+                new ArrayList<>(config.getAtomMatcherList());
+
+        assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat())
+                .getSimpleAtomMatcher().getAtomId());
+        assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
+                findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent())
+                        .getSimpleAtomMatcher().getAtomId());
+
+        StatsdConfigProto.AtomMatcher unionMatcher = findAndRemoveAtomMatcherById(atomMatchers,
+                eventMetric.getWhat());
+        assertNotNull(unionMatcher.getCombination());
+        assertEquals(2, unionMatcher.getCombination().getMatcherCount());
+
+        Integer[] actualAtoms = new Integer[]{
+              findAndRemoveAtomMatcherById(atomMatchers,
+                      unionMatcher.getCombination().getMatcher(0))
+                      .getSimpleAtomMatcher().getAtomId(),
+                findAndRemoveAtomMatcherById(atomMatchers,
+                        unionMatcher.getCombination().getMatcher(1))
+                        .getSimpleAtomMatcher().getAtomId()};
+        Arrays.sort(actualAtoms);
+        assertArrayEquals(pushedAtoms, actualAtoms);
+
+        assertEquals(0, atomMatchers.size());
+    }
+}
diff --git a/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java
new file mode 100644
index 0000000..363fac0
--- /dev/null
+++ b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.statsd.shelltools.testdrive;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for {@link TestDrive}
+ */
+@RunWith(Parameterized.class)
+public class TestDriveTest {
+    /**
+     * Expected results of a single iteration of the paramerized test.
+     */
+    static class Expect {
+        public boolean success;
+        public Integer[] atoms;
+        public boolean onePushedAtomEvent = false;
+        public String extraPackage = null;
+        public String target;
+        public boolean terse = false;
+
+        static Expect success(Integer... atoms) {
+            return new Expect(true, atoms,
+                    TARGET);
+        }
+        Expect(boolean success, Integer[] atoms, String target) {
+            this.success = success;
+            this.atoms = atoms;
+            this.target = target;
+        }
+        static final Expect FAILURE = new Expect(false, null, null);
+        Expect onePushedAtomEvent() {
+            this.onePushedAtomEvent = true;
+            return this;
+        }
+        Expect extraPackage() {
+            this.extraPackage = TestDriveTest.PACKAGE;
+            return this;
+        }
+        Expect terse() {
+            this.terse = true;
+            return this;
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    public String[] mArgs;
+
+    @Parameterized.Parameter(1)
+    public List<String> mConnectedDevices;
+
+    @Parameterized.Parameter(2)
+    public String mDefaultDevice;
+
+    @Parameterized.Parameter(3)
+    public Expect mExpect;
+
+    private static final String TARGET = "target";
+    private static final List<String> TARGET_AND_OTHER = Arrays.asList("otherDevice",
+            TARGET);
+    private static final List<String> TWO_OTHER_DEVICES = Arrays.asList(
+            "other1", "other2");
+    private static final List<String> TARGET_ONLY = Collections.singletonList(TARGET);
+    private static final List<String> NOT_TARGET = Collections.singletonList("other");
+    private static final List<String> NO_DEVICES = Collections.emptyList();
+    private static final String PACKAGE = "extraPackage";
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(
+                new Object[]{new String[]{}, null, null,
+                        Expect.FAILURE},  // Usage explanation
+                new Object[]{new String[]{"244", "245"}, null, null,
+                        Expect.FAILURE},  // Failure looking up connected devices
+                new Object[]{new String[]{"244", "245"}, NO_DEVICES, null,
+                        Expect.FAILURE},  // No connected devices
+                new Object[]{new String[]{"-s", TARGET, "244", "245"}, NOT_TARGET, null,
+                        Expect.FAILURE},  // Wrong device connected
+                new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, null,
+                        Expect.FAILURE},  // Wrong devices connected
+                new Object[]{new String[]{"244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245)},  // If only one device connected, guess that one
+                new Object[]{new String[]{"244", "not_an_atom"}, TARGET_ONLY, null,
+                        Expect.success(244)},  // Ignore non-atoms
+                new Object[]{new String[]{"not_an_atom"}, TARGET_ONLY, null,
+                        Expect.FAILURE},  // Require at least one atom
+                new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, TARGET,
+                        Expect.FAILURE},  // ANDROID_SERIAL specifies non-connected target
+                new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, TARGET,
+                        Expect.success(244, 245)},  // ANDROID_SERIAL specifies a valid target
+                new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, null,
+                        Expect.FAILURE},  // Two connected devices, no indication of which to use
+                new Object[]{new String[]{"-one", "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).onePushedAtomEvent()},
+                new Object[]{new String[]{"-terse", "-one", "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).onePushedAtomEvent().terse()},
+                new Object[]{new String[]{"-one", "-terse", "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).onePushedAtomEvent().terse()},
+                new Object[]{new String[]{"-p", PACKAGE, "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).extraPackage()},
+                new Object[]{new String[]{"-p", PACKAGE, "-one", "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent()},
+                new Object[]{new String[]{"-one", "-p", PACKAGE, "244", "245"}, TARGET_ONLY, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent()},
+                new Object[]{new String[]{"-s", TARGET, "-one", "-p", PACKAGE, "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent()},
+                new Object[]{new String[]{"-one", "-s", TARGET, "-p", PACKAGE, "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent()},
+                new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent()},
+                new Object[]{new String[]{"-terse", "-one", "-p", PACKAGE, "-s", TARGET,
+                        "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()},
+                new Object[]{new String[]{"-one", "-terse", "-p", PACKAGE, "-s", TARGET,
+                        "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()},
+                new Object[]{new String[]{"-one", "-p", PACKAGE, "-terse", "-s", TARGET,
+                        "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()},
+                new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "-terse",
+                        "244", "245"},
+                        TARGET_AND_OTHER, null,
+                        Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}
+        );
+    }
+
+    private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration();
+    private final TestDrive mTestDrive = new TestDrive();
+
+    private static Integer[] collectAtoms(TestDrive.Configuration configuration) {
+        Integer[] result = new Integer[configuration.mPulledAtoms.size()
+                + configuration.mPushedAtoms.size()];
+        int result_index = 0;
+        for (Integer atom : configuration.mPushedAtoms) {
+            result[result_index++] = atom;
+        }
+        for (Integer atom : configuration.mPulledAtoms) {
+            result[result_index++] = atom;
+        }
+        Arrays.sort(result);
+        return result;
+    }
+
+    @Test
+    public void testProcessArgs() {
+        boolean result = mTestDrive.processArgs(mConfiguration, mArgs, mConnectedDevices,
+                mDefaultDevice);
+        if (mExpect.success) {
+            assertTrue(result);
+            assertArrayEquals(mExpect.atoms, collectAtoms(mConfiguration));
+            assertEquals(mExpect.onePushedAtomEvent, mConfiguration.mOnePushedAtomEvent);
+            assertEquals(mExpect.target, mTestDrive.mDeviceSerial);
+            if (mExpect.terse) {
+                assertEquals(TestDrive.TerseDumper.class, mTestDrive.mDumper.getClass());
+            } else {
+                assertEquals(TestDrive.BasicDumper.class, mTestDrive.mDumper.getClass());
+            }
+        } else {
+            assertFalse(result);
+        }
+    }
+}
diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java
index de6e885..2ed2678 100644
--- a/cmds/svc/src/com/android/commands/svc/Svc.java
+++ b/cmds/svc/src/com/android/commands/svc/Svc.java
@@ -93,7 +93,7 @@
     public static final Command[] COMMANDS = new Command[] {
             COMMAND_HELP,
             new PowerCommand(),
-            new WifiCommand(),
+            // `svc wifi` has been migrated to WifiShellCommand
             new UsbCommand(),
             new NfcCommand(),
             new BluetoothCommand(),
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 3893be4..cd751f4 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -42,7 +42,9 @@
                 + "         Sets the functions which, if the device was charging, become current on"
                     + "screen unlock. If function is blank, turn off this feature.\n"
                 + "       svc usb getFunctions\n"
-                + "          Gets the list of currently enabled functions\n\n"
+                + "          Gets the list of currently enabled functions\n"
+                + "       svc usb resetUsbGadget\n"
+                + "          Reset usb gadget\n\n"
                 + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n";
     }
 
@@ -75,6 +77,13 @@
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
                 return;
+            } else if ("resetUsbGadget".equals(args[1])) {
+                try {
+                    usbMgr.resetUsbGadget();
+                } catch (RemoteException e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
+                return;
             }
         }
         System.err.println(longHelp());
diff --git a/cmds/svc/src/com/android/commands/svc/WifiCommand.java b/cmds/svc/src/com/android/commands/svc/WifiCommand.java
deleted file mode 100644
index e31cb53..0000000
--- a/cmds/svc/src/com/android/commands/svc/WifiCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.commands.svc;
-
-import android.os.ServiceManager;
-import android.os.RemoteException;
-import android.net.wifi.IWifiManager;
-import android.content.Context;
-
-public class WifiCommand extends Svc.Command {
-    public WifiCommand() {
-        super("wifi");
-    }
-
-    public String shortHelp() {
-        return "Control the Wi-Fi manager";
-    }
-
-    public String longHelp() {
-        return shortHelp() + "\n"
-                + "\n"
-                + "usage: svc wifi [enable|disable]\n"
-                + "         Turn Wi-Fi on or off.\n\n";
-    }
-
-    public void run(String[] args) {
-        boolean validCommand = false;
-        if (args.length >= 2) {
-            boolean flag = false;
-            if ("enable".equals(args[1])) {
-                flag = true;
-                validCommand = true;
-            } else if ("disable".equals(args[1])) {
-                flag = false;
-                validCommand = true;
-            }
-            if (validCommand) {
-                IWifiManager wifiMgr
-                        = IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE));
-                if (wifiMgr == null) {
-                    System.err.println("Wi-Fi service is not ready");
-                    return;
-                }
-                try {
-                    wifiMgr.setWifiEnabled("com.android.shell", flag);
-                }
-                catch (RemoteException e) {
-                    System.err.println("Wi-Fi operation failed: " + e);
-                }
-                return;
-            }
-        }
-        System.err.println(longHelp());
-    }
-}
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 7431aea..95265e8 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -1,5 +1,24 @@
 #!/system/bin/sh
 
+# `svc wifi` has been migrated to WifiShellCommand,
+# simply perform translation to `cmd wifi set-wifi-enabled` here.
+if [ "x$1" == "xwifi" ]; then
+    # `cmd wifi` by convention uses enabled/disabled
+    # instead of enable/disable
+    if [ "x$2" == "xenable" ]; then
+        exec cmd wifi set-wifi-enabled enabled
+    elif [ "x$2" == "xdisable" ]; then
+        exec cmd wifi set-wifi-enabled disabled
+    else
+        echo "Control the Wi-Fi manager"
+        echo ""
+        echo "usage: svc wifi [enable|disable]"
+        echo "         Turn Wi-Fi on or off."
+        echo ""
+    fi
+    exit 1
+fi
+
 if [ "x$1" == "xdata" ]; then
     if [ "x$2" == "xenable" ]; then
         exec cmd phone data enable
@@ -16,3 +35,4 @@
 
 export CLASSPATH=/system/framework/svc.jar
 exec app_process /system/bin com.android.commands.svc.Svc "$@"
+
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 24d65fb..5f13a5c 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -46,6 +46,10 @@
      * @param args The command-line arguments
      */
     public static void main(String[] args) {
+        // Initialize the telephony module.
+        // TODO: Do it in zygote and RuntimeInit. b/148897549
+        ActivityThread.initializeMainlineModules();
+
       (new Telecom()).run(args);
     }
 
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index c35f7fc..3b14be7 100644
--- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -16,6 +16,7 @@
 
 package com.android.commands.uiautomator;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.UiAutomation;
 import android.graphics.Point;
 import android.hardware.display.DisplayManagerGlobal;
@@ -61,11 +62,14 @@
     public void run(String[] args) {
         File dumpFile = DEFAULT_DUMP_FILE;
         boolean verboseMode = true;
+        boolean allWindows = false;
 
         for (String arg : args) {
             if (arg.equals("--compressed"))
                 verboseMode = false;
-            else if (!arg.startsWith("-")) {
+            else if (arg.equals("--windows")) {
+                allWindows = true;
+            } else if (!arg.startsWith("-")) {
                 dumpFile = new File(arg);
             }
         }
@@ -85,18 +89,28 @@
         try {
             UiAutomation uiAutomation = automationWrapper.getUiAutomation();
             uiAutomation.waitForIdle(1000, 1000 * 10);
-            AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
-            if (info == null) {
-                System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
-                return;
-            }
+            if (allWindows) {
+                AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+                info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+                uiAutomation.setServiceInfo(info);
+                AccessibilityNodeInfoDumper.dumpWindowsToFile(
+                        uiAutomation.getWindowsOnAllDisplays(), dumpFile,
+                        DisplayManagerGlobal.getInstance());
+            } else {
+                AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
+                if (info == null) {
+                    System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
+                    return;
+                }
 
-            Display display =
-                    DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
-            int rotation = display.getRotation();
-            Point size = new Point();
-            display.getSize(size);
-            AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y);
+                Display display =
+                        DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+                int rotation = display.getRotation();
+                Point size = new Point();
+                display.getSize(size);
+                AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x,
+                        size.y);
+            }
         } catch (TimeoutException re) {
             System.err.println("ERROR: could not get idle state.");
             return;
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index 63c51e8..ab198b3 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -16,11 +16,17 @@
 
 package com.android.uiautomator.core;
 
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.Xml;
+import android.view.Display;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
 
 import org.xmlpull.v1.XmlSerializer;
 
@@ -28,6 +34,7 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.List;
 
 /**
  *
@@ -98,6 +105,95 @@
         Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
     }
 
+    /**
+     * Using {@link AccessibilityWindowInfo} this method will dump some window information and
+     * then walk the layout hierarchy of it's
+     * and generates an xml dump to the location specified by <code>dumpFile</code>
+     * @param allWindows All windows indexed by display-id.
+     * @param dumpFile The file to dump to.
+     */
+    public static void dumpWindowsToFile(SparseArray<List<AccessibilityWindowInfo>> allWindows,
+            File dumpFile, DisplayManagerGlobal displayManager) {
+        if (allWindows.size() == 0) {
+            return;
+        }
+        final long startTime = SystemClock.uptimeMillis();
+        try {
+            FileWriter writer = new FileWriter(dumpFile);
+            XmlSerializer serializer = Xml.newSerializer();
+            StringWriter stringWriter = new StringWriter();
+            serializer.setOutput(stringWriter);
+            serializer.startDocument("UTF-8", true);
+            serializer.startTag("", "displays");
+            for (int d = 0, nd = allWindows.size(); d < nd; ++d) {
+                int displayId = allWindows.keyAt(d);
+                Display display = displayManager.getRealDisplay(displayId);
+                if (display == null) {
+                    continue;
+                }
+                final List<AccessibilityWindowInfo> windows = allWindows.valueAt(d);
+                if (windows.isEmpty()) {
+                    continue;
+                }
+                serializer.startTag("", "display");
+                serializer.attribute("", "id", Integer.toString(displayId));
+                int rotation = display.getRotation();
+                Point size = new Point();
+                display.getSize(size);
+                for (int i = 0, n = windows.size(); i < n; ++i) {
+                    dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation);
+                }
+                serializer.endTag("", "display");
+            }
+            serializer.endTag("", "displays");
+            serializer.endDocument();
+            writer.write(stringWriter.toString());
+            writer.close();
+        } catch (IOException e) {
+            Log.e(LOGTAG, "failed to dump window to file", e);
+        }
+        final long endTime = SystemClock.uptimeMillis();
+        Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
+    }
+
+    private static void dumpWindowRec(AccessibilityWindowInfo winfo, XmlSerializer serializer,
+            int index, int width, int height, int rotation) throws IOException {
+        serializer.startTag("", "window");
+        serializer.attribute("", "index", Integer.toString(index));
+        final CharSequence title = winfo.getTitle();
+        serializer.attribute("", "title", title != null ? title.toString() : "");
+        final Rect tmpBounds = new Rect();
+        winfo.getBoundsInScreen(tmpBounds);
+        serializer.attribute("", "bounds", tmpBounds.toShortString());
+        serializer.attribute("", "active", Boolean.toString(winfo.isActive()));
+        serializer.attribute("", "focused", Boolean.toString(winfo.isFocused()));
+        serializer.attribute("", "accessibility-focused",
+                Boolean.toString(winfo.isAccessibilityFocused()));
+        serializer.attribute("", "id", Integer.toString(winfo.getId()));
+        serializer.attribute("", "layer", Integer.toString(winfo.getLayer()));
+        serializer.attribute("", "type", AccessibilityWindowInfo.typeToString(winfo.getType()));
+        int count = winfo.getChildCount();
+        for (int i = 0; i < count; ++i) {
+            AccessibilityWindowInfo child = winfo.getChild(i);
+            if (child == null) {
+                Log.i(LOGTAG, String.format("Null window child %d/%d, parent: %s", i, count,
+                        winfo.getTitle()));
+                continue;
+            }
+            dumpWindowRec(child, serializer, i, width, height, rotation);
+            child.recycle();
+        }
+        AccessibilityNodeInfo root = winfo.getRoot();
+        if (root != null) {
+            serializer.startTag("", "hierarchy");
+            serializer.attribute("", "rotation", Integer.toString(rotation));
+            dumpNodeRec(root, serializer, 0, width, height);
+            root.recycle();
+            serializer.endTag("", "hierarchy");
+        }
+        serializer.endTag("", "window");
+    }
+
     private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index,
             int width, int height) throws IOException {
         serializer.startTag("", "node");
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
index 455e4bb..b23bf5d 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -67,7 +67,7 @@
                     throw new IllegalStateException("Could not find provider: " + providerName);
                 }
                 provider = holder.provider;
-                cursor = provider.query(null, Settings.Secure.CONTENT_URI,
+                cursor = provider.query(null, null, Settings.Secure.CONTENT_URI,
                         new String[] {
                             Settings.Secure.VALUE
                         },