Snap for 8902501 from abcad7b838eec4a525da9adcd0884552fbf8ea21 to mainline-go-networking-release

Change-Id: Ie7b77bb324516e6b2825d074365c5072d7eb57b5
diff --git a/service/Android.bp b/service/Android.bp
index bad74ce..5e96b1e 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -25,6 +25,7 @@
 
 java_library {
     name: "service-statsd",
+    defaults: ["standalone-system-server-module-optimize-defaults"],
     srcs: [ ":service-statsd-sources" ],
     sdk_version: "system_server_current",
     libs: [
diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp
index 5b7a1c1..b32a170 100644
--- a/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/statsd/src/metrics/DurationMetricProducer.cpp
@@ -434,27 +434,28 @@
     onSlicedConditionMayChangeInternalLocked(overallCondition, eventTime);
 }
 
-void DurationMetricProducer::onActiveStateChangedLocked(const int64_t eventTimeNs) {
-    MetricProducer::onActiveStateChangedLocked(eventTimeNs);
+void DurationMetricProducer::onActiveStateChangedLocked(const int64_t eventTimeNs,
+                                                        const bool isActive) {
+    MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
 
     if (!mConditionSliced) {
         if (ConditionState::kTrue != mCondition) {
             return;
         }
 
-        if (mIsActive) {
+        if (isActive) {
             flushIfNeededLocked(eventTimeNs);
         }
 
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
+            whatIt.second->onConditionChanged(isActive, eventTimeNs);
         }
-    } else if (mIsActive) {
+    } else if (isActive) {
         flushIfNeededLocked(eventTimeNs);
-        onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
-    } else { // mConditionSliced == true && !mIsActive
+        onSlicedConditionMayChangeInternalLocked(isActive, eventTimeNs);
+    } else {  // mConditionSliced == true && !isActive
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
+            whatIt.second->onConditionChanged(isActive, eventTimeNs);
         }
     }
 }
diff --git a/statsd/src/metrics/DurationMetricProducer.h b/statsd/src/metrics/DurationMetricProducer.h
index 206882b..ba5fe95 100644
--- a/statsd/src/metrics/DurationMetricProducer.h
+++ b/statsd/src/metrics/DurationMetricProducer.h
@@ -99,7 +99,7 @@
     void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
 
     // Internal interface to handle active state change.
-    void onActiveStateChangedLocked(const int64_t eventTimeNs) override;
+    void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
     // Internal interface to handle sliced condition change.
     void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp
index 1627742..faa284c 100644
--- a/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -406,12 +406,15 @@
     }
 }
 
-void GaugeMetricProducer::onActiveStateChangedLocked(const int64_t eventTimeNs) {
-    MetricProducer::onActiveStateChangedLocked(eventTimeNs);
-    if (ConditionState::kTrue != mCondition || !mIsPulled) {
+void GaugeMetricProducer::onActiveStateChangedLocked(const int64_t eventTimeNs,
+                                                     const bool isActive) {
+    MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
+
+    if (ConditionState::kTrue != mCondition) {
         return;
     }
-    if (mTriggerAtomId == -1 || (mIsActive && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE)) {
+
+    if (isActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
         pullAndMatchEventsLocked(eventTimeNs);
     }
 }
@@ -426,7 +429,9 @@
     }
 
     flushIfNeededLocked(eventTimeNs);
-    if (conditionMet && mIsPulled && mTriggerAtomId == -1) {
+    if (conditionMet && mIsPulled &&
+        (mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE ||
+         mSamplingType == GaugeMetric::CONDITION_CHANGE_TO_TRUE)) {
         pullAndMatchEventsLocked(eventTimeNs);
     }  // else: Push mode. No need to proactively pull the gauge data.
 }
@@ -641,7 +646,7 @@
             VLOG("Gauge gauge metric %lld, dump key value: %s", (long long)mMetricId,
                  slice.first.toString().c_str());
         }
-    } else {
+    } else if (mIsActive) {
         mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
         mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
         if (!maxDropEventsReached()) {
diff --git a/statsd/src/metrics/GaugeMetricProducer.h b/statsd/src/metrics/GaugeMetricProducer.h
index 8bd4a8c..af99535 100644
--- a/statsd/src/metrics/GaugeMetricProducer.h
+++ b/statsd/src/metrics/GaugeMetricProducer.h
@@ -120,7 +120,7 @@
     void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
 
     // Internal interface to handle active state change.
-    void onActiveStateChangedLocked(const int64_t eventTimeNs) override;
+    void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
     // Internal interface to handle sliced condition change.
     void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp
index 17aa312..75c2476 100644
--- a/statsd/src/metrics/MetricProducer.cpp
+++ b/statsd/src/metrics/MetricProducer.cpp
@@ -194,9 +194,13 @@
     if (!mIsActive) {
         return;
     }
-    mIsActive = evaluateActiveStateLocked(elapsedTimestampNs);
-    if (!mIsActive) {
-        onActiveStateChangedLocked(elapsedTimestampNs);
+    const bool isActive = evaluateActiveStateLocked(elapsedTimestampNs);
+    if (!isActive) {  // Metric went from active to not active.
+        onActiveStateChangedLocked(elapsedTimestampNs, false);
+
+        // Set mIsActive to false after onActiveStateChangedLocked to ensure any pulls that occur
+        // through onActiveStateChangedLocked are processed.
+        mIsActive = false;
     }
 }
 
@@ -215,10 +219,12 @@
     }
     activation->start_ns = elapsedTimestampNs;
     activation->state = ActivationState::kActive;
-    bool oldActiveState = mIsActive;
-    mIsActive = true;
-    if (!oldActiveState) { // Metric went from not active to active.
-        onActiveStateChangedLocked(elapsedTimestampNs);
+    if (!mIsActive) {  // Metric was previously inactive and now is active.
+        // Set mIsActive to true before onActiveStateChangedLocked to ensure any pulls that occur
+        // through onActiveStateChangedLocked are processed.
+        mIsActive = true;
+
+        onActiveStateChangedLocked(elapsedTimestampNs, true);
     }
 }
 
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 95f8dd4..ab74951 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -394,7 +394,7 @@
     /**
      * Flushes all the data including the current partial bucket.
      */
-    virtual void flushLocked(const int64_t& eventTimeNs) {
+    void flushLocked(const int64_t& eventTimeNs) {
         flushIfNeededLocked(eventTimeNs);
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
     };
@@ -445,8 +445,8 @@
 
     bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
 
-    virtual void onActiveStateChangedLocked(const int64_t eventTimeNs) {
-        if (!mIsActive) {
+    virtual void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) {
+        if (!isActive) {
             flushLocked(eventTimeNs);
         }
     }
diff --git a/statsd/src/metrics/NumericValueMetricProducer.cpp b/statsd/src/metrics/NumericValueMetricProducer.cpp
index 703b07b..4f8ae23 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.cpp
+++ b/statsd/src/metrics/NumericValueMetricProducer.cpp
@@ -128,10 +128,11 @@
     protoOutput->end(valueToken);
 }
 
-void NumericValueMetricProducer::onActiveStateChangedInternalLocked(const int64_t eventTimeNs) {
+void NumericValueMetricProducer::onActiveStateChangedInternalLocked(const int64_t eventTimeNs,
+                                                                    const bool isActive) {
     // When active state changes from true to false for pulled metric, clear diff base but don't
     // reset other counters as we may accumulate more value in the bucket.
-    if (mUseDiff && !mIsActive) {
+    if (mUseDiff && !isActive) {
         resetBase();
     }
 }
diff --git a/statsd/src/metrics/NumericValueMetricProducer.h b/statsd/src/metrics/NumericValueMetricProducer.h
index 8eca3f8..1246815 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.h
+++ b/statsd/src/metrics/NumericValueMetricProducer.h
@@ -66,7 +66,8 @@
         return config.value_metric(configIndex).links();
     }
 
-    void onActiveStateChangedInternalLocked(const int64_t eventTimeNs) override;
+    void onActiveStateChangedInternalLocked(const int64_t eventTimeNs,
+                                            const bool isActive) override;
 
     // Only called when mIsActive and the event is NOT too late.
     void onConditionChangedInternalLocked(const ConditionState oldCondition,
diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp
index f829cb4..0500284 100644
--- a/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/statsd/src/metrics/ValueMetricProducer.cpp
@@ -447,19 +447,24 @@
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::skipCurrentBucket(
         const int64_t dropTimeNs, const BucketDropReason reason) {
+    if (!mIsActive) {
+        // Don't keep track of skipped buckets if metric is not active.
+        return;
+    }
+
     if (!maxDropEventsReached()) {
         mCurrentSkippedBucket.dropEvents.push_back(buildDropEvent(dropTimeNs, reason));
     }
     mCurrentBucketIsSkipped = true;
 }
 
-// Handle active state change. Active state change is treated like a condition change:
+// Handle active state change. Active state change is *mostly* treated like a condition change:
 // - drop bucket if active state change event arrives too late
 // - if condition is true, pull data on active state changes
 // - ConditionTimer tracks changes based on AND of condition and active state.
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::onActiveStateChangedLocked(
-        const int64_t eventTimeNs) {
+        const int64_t eventTimeNs, const bool isActive) {
     const bool eventLate = isEventLateLocked(eventTimeNs);
     if (eventLate) {
         // Drop bucket because event arrived too late, ie. we are missing data for this bucket.
@@ -467,10 +472,9 @@
         invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
     }
 
-    // Call parent method once we've verified the validity of current bucket.
-    MetricProducer::onActiveStateChangedLocked(eventTimeNs);
-
     if (ConditionState::kTrue != mCondition) {
+        // Call parent method before early return.
+        MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
         return;
     }
 
@@ -480,15 +484,17 @@
             pullAndMatchEventsLocked(eventTimeNs);
         }
 
-        onActiveStateChangedInternalLocked(eventTimeNs);
+        onActiveStateChangedInternalLocked(eventTimeNs, isActive);
     }
 
-    flushIfNeededLocked(eventTimeNs);
+    // Once any pulls are processed, call through to parent method which might flush the current
+    // bucket.
+    MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
 
     // Let condition timer know of new active state.
-    mConditionTimer.onConditionChanged(mIsActive, eventTimeNs);
+    mConditionTimer.onConditionChanged(isActive, eventTimeNs);
 
-    updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs);
+    updateCurrentSlicedBucketConditionTimers(isActive, eventTimeNs);
 }
 
 template <typename AggregatedValue, typename DimExtras>
diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h
index 956580a..80d0bb6 100644
--- a/statsd/src/metrics/ValueMetricProducer.h
+++ b/statsd/src/metrics/ValueMetricProducer.h
@@ -167,9 +167,10 @@
     void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
 
     // ValueMetricProducer internal interface to handle active state change.
-    void onActiveStateChangedLocked(const int64_t eventTimeNs) override;
+    void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
-    virtual void onActiveStateChangedInternalLocked(const int64_t eventTimeNs) {
+    virtual void onActiveStateChangedInternalLocked(const int64_t eventTimeNs,
+                                                    const bool isActive) {
     }
 
     // ValueMetricProducer internal interface to handle condition change.
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 1f79f87..984b72e 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -61,6 +61,7 @@
     gaugeMetric->set_max_pull_delay_sec(INT_MAX);
     config.set_hash_strings_in_metric_report(false);
     gaugeMetric->set_split_bucket_for_app_upgrade(true);
+    gaugeMetric->set_min_bucket_size_nanos(1000);
 
     return config;
 }
@@ -441,10 +442,10 @@
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()
-                                 ->second->mAllMetricProducers[0]
-                                 ->getCurrentBucketNum();
-    EXPECT_GT(startBucketNum, (int64_t)0);
+    const int startBucketNum = processor->mMetricsManagers.begin()
+                                       ->second->mAllMetricProducers[0]
+                                       ->getCurrentBucketNum();
+    EXPECT_EQ(startBucketNum, 2);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // When creating the config, the gauge metric producer should register the alarm at the
@@ -470,13 +471,28 @@
     EXPECT_EQ(pulledAtomStats.atom_id(), ATOM_TAG);
     EXPECT_EQ(pulledAtomStats.total_pull(), 0);
 
+    // Check skipped bucket is not added when metric is not active.
+    int64_t dumpReportTimeNs = appUpgradeTimeNs + 1;  // 10 mins + 3 ns.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &buffer);
+    ConfigMetricsReportList reports;
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics =
+            reports.reports(0).metrics(0).gauge_metrics();
+    EXPECT_EQ(gaugeMetrics.skipped_size(), 0);
+
     // Pulling alarm arrives on time and reset the sequential pulling alarm.
     // Event should not be kept.
     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.
+    // Activate the metric. A pull occurs upon activation. The event is kept. 1 total
+    // 15 mins + 2 ms
     const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000);  // 2 millis.
     auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs);
     processor->OnLogEvent(batterySaverOnEvent.get());  // 15 mins + 2 ms.
@@ -491,19 +507,27 @@
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs);
 
     // Create random event to deactivate metric.
-    auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50);
+    // A pull should not occur here. 3 total.
+    // 25 mins + 2 ms + 1 ns.
+    const int64_t deactivationNs = activationNs + ttlNs + 1;
+    auto deactivationEvent = CreateScreenBrightnessChangedEvent(deactivationNs, 50);
     processor->OnLogEvent(deactivationEvent.get());
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // Event should not be kept. 3 total.
+    // 30 mins + 3 ns.
     processor->informPullAlarmFired(nextPullTimeNs + 3);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs);
 
+    // Event should not be kept. 3 total.
+    // 35 mins + 2 ns.
     processor->informPullAlarmFired(nextPullTimeNs + 2);
+    EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, nextPullTimeNs);
 
-    ConfigMetricsReportList reports;
-    vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true,
+    buffer.clear();
+    // 40 mins + 10 ns.
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 6 * bucketSizeNs + 10,
+                            false /* include_current_partial_bucket */, true /* erase_data */,
                             ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
@@ -513,7 +537,7 @@
     backfillAggregatedAtoms(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
-    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    gaugeMetrics = StatsLogReport::GaugeMetricDataWrapper();
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
     ASSERT_GT((int)gaugeMetrics.data_size(), 0);
 
@@ -552,10 +576,21 @@
     ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size());
     EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)),
               bucketInfo.start_bucket_elapsed_nanos());
-    EXPECT_EQ(MillisToNano(NanoToMillis(activationNs + ttlNs + 1)),
-              bucketInfo.end_bucket_elapsed_nanos());
+    EXPECT_EQ(MillisToNano(NanoToMillis(deactivationNs)), 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);
+
+    // Check skipped bucket is not added after deactivation.
+    dumpReportTimeNs = configAddedTimeNs + 8 * bucketSizeNs + 10;
+    buffer.clear();
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    gaugeMetrics = reports.reports(0).metrics(0).gauge_metrics();
+    EXPECT_EQ(gaugeMetrics.skipped_size(), 0);
 }
 
 TEST_F(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition) {
diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index 996c2fb..385a473 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -61,6 +61,7 @@
     valueMetric->set_skip_zero_diff_output(false);
     valueMetric->set_max_pull_delay_sec(INT_MAX);
     valueMetric->set_split_bucket_for_app_upgrade(true);
+    valueMetric->set_min_bucket_size_nanos(1000);
     return config;
 }
 
@@ -433,10 +434,10 @@
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
     processor->mPullerManager->ForceClearPullerCache();
 
-    int startBucketNum = processor->mMetricsManagers.begin()
-                                 ->second->mAllMetricProducers[0]
-                                 ->getCurrentBucketNum();
-    EXPECT_GT(startBucketNum, (int64_t)0);
+    const int startBucketNum = processor->mMetricsManagers.begin()
+                                       ->second->mAllMetricProducers[0]
+                                       ->getCurrentBucketNum();
+    EXPECT_EQ(startBucketNum, 2);
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
     // When creating the config, the value metric producer should register the alarm at the
@@ -448,61 +449,109 @@
             processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs;
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
 
-    // Check no pull occurred on metric initialization when it's not active.
+    // Initialize metric.
     const int64_t metricInitTimeNs = configAddedTimeNs + 1;  // 10 mins + 1 ns.
     processor->onStatsdInitCompleted(metricInitTimeNs);
+
+    // Check no pull occurred since metric not active.
     StatsdStatsReport_PulledAtomStats pulledAtomStats = getPulledAtomStats();
     EXPECT_EQ(pulledAtomStats.atom_id(), util::SUBSYSTEM_SLEEP_STATE);
     EXPECT_EQ(pulledAtomStats.total_pull(), 0);
 
-    // Check no pull occurred on app upgrade when metric is not active.
-    const int64_t appUpgradeTimeNs = metricInitTimeNs + 1;  // 10 mins + 2 ns.
-    processor->notifyAppUpgrade(appUpgradeTimeNs, "appName", 1000 /* uid */, 2 /* version */);
-    pulledAtomStats = getPulledAtomStats();
-    EXPECT_EQ(pulledAtomStats.atom_id(), util::SUBSYSTEM_SLEEP_STATE);
-    EXPECT_EQ(pulledAtomStats.total_pull(), 0);
-
-    // Check no pull occurred on dump report when metric is not active.
-    int64_t dumpReportTimeNs = appUpgradeTimeNs + 1;  // 10 mins + 3 ns.
+    // Check skip bucket is not added when metric is not active.
+    int64_t dumpReportTimeNs = metricInitTimeNs + 1;  // 10 mins + 2 ns.
     vector<uint8_t> buffer;
-    processor->onDumpReport(cfgKey, dumpReportTimeNs, true, true, ADB_DUMP, NO_TIME_CONSTRAINTS,
-                            &buffer);
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, FAST, &buffer);
+    ConfigMetricsReportList reports;
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics =
+            reports.reports(0).metrics(0).value_metrics();
+    EXPECT_EQ(valueMetrics.skipped_size(), 0);
+
+    // App upgrade.
+    const int64_t appUpgradeTimeNs = dumpReportTimeNs + 1;  // 10 mins + 3 ns.
+    processor->notifyAppUpgrade(appUpgradeTimeNs, "appName", 1000 /* uid */, 2 /* version */);
+
+    // Check no pull occurred since metric not active.
     pulledAtomStats = getPulledAtomStats();
     EXPECT_EQ(pulledAtomStats.atom_id(), util::SUBSYSTEM_SLEEP_STATE);
     EXPECT_EQ(pulledAtomStats.total_pull(), 0);
 
-    // Pulling alarm arrives on time and reset the sequential pulling alarm.
+    // Check skip bucket is not added when metric is not active.
+    dumpReportTimeNs = appUpgradeTimeNs + 1;  // 10 mins + 4 ns.
+    buffer.clear();
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    valueMetrics = reports.reports(0).metrics(0).value_metrics();
+    EXPECT_EQ(valueMetrics.skipped_size(), 0);
+
+    // Dump report with a pull. The pull should not happen because metric is inactive.
+    dumpReportTimeNs = dumpReportTimeNs + 1;  // 10 mins + 6 ns.
+    buffer.clear();
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &buffer);
+    pulledAtomStats = getPulledAtomStats();
+    EXPECT_EQ(pulledAtomStats.atom_id(), util::SUBSYSTEM_SLEEP_STATE);
+    EXPECT_EQ(pulledAtomStats.total_pull(), 0);
+
+    // Check skipped bucket is not added from the dump operation when metric is not active.
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    valueMetrics = reports.reports(0).metrics(0).value_metrics();
+    EXPECT_EQ(valueMetrics.skipped_size(), 0);
+
+    // Pulling alarm arrives on time and reset the sequential pulling alarm. This bucket is skipped.
     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
+    // Activate the metric. A pull occurs here that sets the base.
+    // 15 mins + 2 ms
     const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000);  // 2 millis.
     auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs);
     processor->OnLogEvent(batterySaverOnEvent.get());  // 15 mins + 2 ms.
     EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
+    // This bucket should be kept. 1 total
     processor->informPullAlarmFired(expectedPullTimeNs + 1);  // 20 mins + 1 ns.
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, expectedPullTimeNs);
 
+    // 25 mins + 2 ns.
+    // This bucket should be kept. 2 total
     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(activationNs + ttlNs + 1, 50);
+    // A pull occurs here and a partial bucket is created. The bucket ending here is kept. 3 total.
+    // 25 mins + 2 ms + 1 ns.
+    const int64_t deactivationNs = activationNs + ttlNs + 1;
+    auto deactivationEvent = CreateScreenBrightnessChangedEvent(deactivationNs, 50);
     processor->OnLogEvent(deactivationEvent.get());
     EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive());
 
+    // 30 mins + 3 ns. This bucket is skipped.
     processor->informPullAlarmFired(expectedPullTimeNs + 3);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, expectedPullTimeNs);
 
+    // 35 mins + 4 ns. This bucket is skipped
     processor->informPullAlarmFired(expectedPullTimeNs + 4);
     EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs);
 
-    ConfigMetricsReportList reports;
+    dumpReportTimeNs = configAddedTimeNs + 6 * bucketSizeNs + 10;
     buffer.clear();
-    processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true,
-                            ADB_DUMP, FAST, &buffer);
+    // 40 mins + 10 ns.
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, false /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, FAST, &buffer);
     EXPECT_TRUE(buffer.size() > 0);
     EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
     backfillDimensionPath(&reports);
@@ -510,7 +559,7 @@
     backfillStartEndTimestamp(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
-    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    valueMetrics = StatsLogReport::ValueMetricDataWrapper();
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
     ASSERT_GT((int)valueMetrics.data_size(), 0);
 
@@ -520,8 +569,8 @@
     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.
-    ASSERT_EQ(2, data.bucket_info_size());
+    // We have 3 full buckets, the two surrounding the activation are dropped.
+    ASSERT_EQ(3, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
     EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
@@ -532,6 +581,25 @@
     EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos());
     EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
     ASSERT_EQ(1, bucketInfo.values_size());
+
+    bucketInfo = data.bucket_info(2);
+    EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)),
+              bucketInfo.start_bucket_elapsed_nanos());
+    EXPECT_EQ(MillisToNano(NanoToMillis(deactivationNs)), bucketInfo.end_bucket_elapsed_nanos());
+    ASSERT_EQ(1, bucketInfo.values_size());
+
+    // Check skipped bucket is not added after deactivation.
+    dumpReportTimeNs = configAddedTimeNs + 7 * bucketSizeNs + 10;
+    buffer.clear();
+    // 45 mins + 10 ns.
+    processor->onDumpReport(cfgKey, dumpReportTimeNs, true /* include_current_partial_bucket */,
+                            true /* erase_data */, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    valueMetrics = reports.reports(0).metrics(0).value_metrics();
+    EXPECT_EQ(valueMetrics.skipped_size(), 0);
 }
 
 /**
diff --git a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
index 0cf5bbb..adfc565 100644
--- a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -319,11 +319,8 @@
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
     assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
-    // Bucket is skipped because metric is not activated.
-    assertThat(metricReport.getValueMetrics().getSkippedList()).isNotEmpty();
-    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEventList()).isNotEmpty();
-    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEvent(0).getDropReason())
-            .isEqualTo(BucketDropReason.NO_DATA);
+    // Skipped buckets are not added when metric is empty.
+    assertThat(metricReport.getValueMetrics().getSkippedList()).isEmpty();
   }
 
     public void testValueMetricWithConditionAndActivation() throws Exception {