Statsd cpu optimizaton

- cache the changed dimensions in condition tracker.
- avoid query condition wizard when unnecessary.
- avoid copy dimension keys in condition key generation.

Test: statsd tests.

BUG: b/73959649
Change-Id: I17d68e2a82643de3f421309841e75f84c6fd8f43
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 605198f..7f0a26c 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -199,7 +199,9 @@
     tests/e2e/MetricConditionLink_e2e_test.cpp \
     tests/e2e/Attribution_e2e_test.cpp \
     tests/e2e/GaugeMetric_e2e_test.cpp \
-    tests/e2e/DimensionInCondition_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
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
@@ -247,11 +249,32 @@
 LOCAL_MODULE := statsd_benchmark
 
 LOCAL_SRC_FILES := $(statsd_common_src) \
+                    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/filter_value_benchmark.cpp \
+                   benchmark/get_dimensions_for_condition_benchmark.cpp \
+                   benchmark/metric_util.cpp \
+                   benchmark/duration_metric_benchmark.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+    $(statsd_common_static_libraries)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+
+LOCAL_PROTOC_FLAGS := \
+    -Iexternal/protobuf/src
+
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+                        libprotobuf-cpp-full
+
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    platformprotoslite
 
 LOCAL_C_INCLUDES := $(statsd_common_c_includes)
 
@@ -285,4 +308,4 @@
 statsd_common_shared_libraries:=
 
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/benchmark/duration_metric_benchmark.cpp b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
new file mode 100644
index 0000000..2631009
--- /dev/null
+++ b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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 <vector>
+#include "benchmark/benchmark.h"
+#include "FieldValue.h"
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "metric_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+static StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+    StatsdConfig config;
+    *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.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;
+}
+
+static StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition(
+        DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+    StatsdConfig config;
+    *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;
+}
+
+static void BM_DurationMetricNoLink(benchmark::State& state) {
+    ConfigKey cfgKey;
+    auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+            DurationMetric::SUM, false);
+    int64_t bucketStartTimeNs = 10000000000;
+    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(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);
+
+    while (state.KeepRunning()) {
+        auto processor = CreateStatsLogProcessor(
+                bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+    }
+}
+
+BENCHMARK(BM_DurationMetricNoLink);
+
+
+static void BM_DurationMetricLink(benchmark::State& state) {
+    ConfigKey cfgKey;
+    auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition(
+        DurationMetric::SUM, false);
+    int64_t bucketStartTimeNs = 10000000000;
+    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(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);
+
+    while (state.KeepRunning()) {
+        auto processor = CreateStatsLogProcessor(
+                bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+    }
+}
+
+BENCHMARK(BM_DurationMetricLink);
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index b9ddf36..66c4def 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -18,6 +18,7 @@
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
 #include "logd/LogEvent.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -25,17 +26,29 @@
 
 using std::vector;
 
+static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matcher) {
+    AttributionNodeInternal node;
+    node.set_uid(100);
+    node.set_tag("LOCATION");
+
+    std::vector<AttributionNodeInternal> nodes = {node, node};
+    event->write(nodes);
+    event->write(3.2f);
+    event->write("LOCATION");
+    event->write((int64_t)990);
+    event->init();
+
+    field_matcher->set_field(1);
+    auto child = field_matcher->add_child();
+    child->set_field(1);
+    child->set_position(FIRST);
+    child->add_child()->set_field(1);
+}
+
 static void BM_FilterValue(benchmark::State& state) {
     LogEvent event(1, 100000);
-    event.write(3.2f);
-    event.write("LOCATION");
-    event.write((int64_t)990);
-    event.init();
-
     FieldMatcher field_matcher;
-    field_matcher.set_field(1);
-    field_matcher.add_child()->set_field(2);
-    field_matcher.add_child()->set_field(3);
+    createLogEventAndMatcher(&event, &field_matcher);
 
     std::vector<Matcher> matchers;
     translateFieldMatcher(field_matcher, &matchers);
@@ -48,6 +61,22 @@
 
 BENCHMARK(BM_FilterValue);
 
+static void BM_FilterValue2(benchmark::State& state) {
+    LogEvent event(1, 100000);
+    FieldMatcher field_matcher;
+    createLogEventAndMatcher(&event, &field_matcher);
+
+    std::vector<Matcher> matchers;
+    translateFieldMatcher(field_matcher, &matchers);
+
+    while (state.KeepRunning()) {
+        HashableDimensionKey output;
+        filterValues(matchers, event.getValues(), &output);
+    }
+}
+
+BENCHMARK(BM_FilterValue2);
+
 }  //  namespace statsd
 }  //  namespace os
 }  //  namespace android
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
new file mode 100644
index 0000000..2a4403e
--- /dev/null
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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 <vector>
+#include "benchmark/benchmark.h"
+#include "FieldValue.h"
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) {
+    AttributionNodeInternal node;
+    node.set_uid(100);
+    node.set_tag("LOCATION");
+
+    std::vector<AttributionNodeInternal> nodes = {node, node};
+    event->write(nodes);
+    event->write(3.2f);
+    event->write("LOCATION");
+    event->write((int64_t)990);
+    event->init();
+
+    link->conditionId = 1;
+
+    FieldMatcher field_matcher;
+    field_matcher.set_field(event->GetTagId());
+    auto child = field_matcher.add_child();
+    child->set_field(1);
+    child->set_position(FIRST);
+    child->add_child()->set_field(1);
+
+    translateFieldMatcher(field_matcher, &link->metricFields);
+    field_matcher.set_field(event->GetTagId() + 1);
+    translateFieldMatcher(field_matcher, &link->conditionFields);
+}
+
+static void BM_GetDimensionInCondition(benchmark::State& state) {
+    Metric2Condition link;
+    LogEvent event(1, 100000);
+    createLogEventAndLink(&event, &link);
+
+    while (state.KeepRunning()) {
+        HashableDimensionKey output;
+        getDimensionForCondition(event.getValues(), link, &output);
+    }
+}
+
+BENCHMARK(BM_GetDimensionInCondition);
+
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
new file mode 100644
index 0000000..b67764b
--- /dev/null
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -0,0 +1,394 @@
+// 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 "metric_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
+    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(atomId);
+    return atom_matcher;
+}
+
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
+                                                      ScheduledJobStateChanged::State 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::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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateStartScheduledJobAtomMatcher() {
+    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart",
+                                                     ScheduledJobStateChanged::STARTED);
+}
+
+AtomMatcher CreateFinishScheduledJobAtomMatcher() {
+    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish",
+                                                     ScheduledJobStateChanged::FINISHED);
+}
+
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
+    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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateUidProcessStateChangedAtomMatcher() {
+    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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
+                                                  WakelockStateChanged::State 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::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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateAcquireWakelockAtomMatcher() {
+    return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE);
+}
+
+AtomMatcher CreateReleaseWakelockAtomMatcher() {
+    return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
+}
+
+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(android::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",
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+}
+
+AtomMatcher CreateScreenTurnedOffAtomMatcher() {
+    return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff",
+            ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+}
+
+AtomMatcher CreateSyncStateChangedAtomMatcher(
+    const string& name, SyncStateChanged::State 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::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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateSyncStartAtomMatcher() {
+    return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON);
+}
+
+AtomMatcher CreateSyncEndAtomMatcher() {
+    return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF);
+}
+
+AtomMatcher CreateActivityForegroundStateChangedAtomMatcher(
+    const string& name, ActivityForegroundStateChanged::Activity activity) {
+    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);
+    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(activity);
+    return atom_matcher;
+}
+
+AtomMatcher CreateMoveToBackgroundAtomMatcher() {
+    return CreateActivityForegroundStateChangedAtomMatcher(
+        "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND);
+}
+
+AtomMatcher CreateMoveToForegroundAtomMatcher() {
+    return CreateActivityForegroundStateChangedAtomMatcher(
+        "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND);
+}
+
+Predicate CreateScheduledJobPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
+    return predicate;
+}
+
+Predicate CreateBatterySaverModePredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("BatterySaverIsOn"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
+    return predicate;
+}
+
+Predicate CreateScreenIsOnPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("ScreenIsOn"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
+    return predicate;
+}
+
+Predicate CreateScreenIsOffPredicate() {
+    Predicate predicate;
+    predicate.set_id(1111123);
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
+    return predicate;
+}
+
+Predicate CreateHoldingWakelockPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("HoldingWakelock"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
+    return predicate;
+}
+
+Predicate CreateIsSyncingPredicate() {
+    Predicate predicate;
+    predicate.set_id(33333333333333);
+    predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
+    return predicate;
+}
+
+Predicate CreateIsInBackgroundPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("IsInBackground"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
+    return predicate;
+}
+
+void addPredicateToPredicateCombination(const Predicate& predicate,
+                                        Predicate* combinationPredicate) {
+    combinationPredicate->mutable_combination()->add_predicate(predicate.id());
+}
+
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+                                            const std::vector<Position>& positions) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const auto position : positions) {
+        auto child = dimensions.add_child();
+        child->set_field(1);
+        child->set_position(position);
+        child->add_child()->set_field(1);
+    }
+    return dimensions;
+}
+
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+                                                 const std::vector<Position>& positions) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const auto position : positions) {
+        auto child = dimensions.add_child();
+        child->set_field(1);
+        child->set_position(position);
+        child->add_child()->set_field(1);
+        child->add_child()->set_field(2);
+    }
+    return dimensions;
+}
+
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const int field : fields) {
+        dimensions.add_child()->set_field(field);
+    }
+    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;
+}
+
+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;
+
+}
+
+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;
+}
+
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+    const std::vector<AttributionNodeInternal>& attributions,
+    const string& name, uint64_t timestampNs) {
+    return CreateScheduledJobStateChangedEvent(
+            attributions, name, 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> 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::Activity activity, 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(activity);
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
+    return CreateActivityForegroundStateChangedEvent(
+        uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
+    return CreateActivityForegroundStateChangedEvent(
+        uid, ActivityForegroundStateChanged::MOVE_TO_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);
+}
+
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+                                              const ConfigKey& key) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    sp<StatsLogProcessor> processor = new StatsLogProcessor(
+        uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});
+    processor->OnConfigUpdated(key, config);
+    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) {
+              return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs();
+            });
+}
+
+int64_t StringToId(const string& str) {
+    return static_cast<int64_t>(std::hash<std::string>()(str));
+}
+
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
new file mode 100644
index 0000000..9b28d60
--- /dev/null
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -0,0 +1,161 @@
+// 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 "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 "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Create AtomMatcher proto to simply match a specific atom type.
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+
+// Create AtomMatcher proto for scheduled job state changed.
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
+
+// Create AtomMatcher proto for starting a scheduled job.
+AtomMatcher CreateStartScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for a scheduled job is done.
+AtomMatcher CreateFinishScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for screen brightness state changed.
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
+
+// Create AtomMatcher proto for acquiring wakelock.
+AtomMatcher CreateAcquireWakelockAtomMatcher();
+
+// Create AtomMatcher proto for releasing wakelock.
+AtomMatcher CreateReleaseWakelockAtomMatcher() ;
+
+// Create AtomMatcher proto for screen turned on.
+AtomMatcher CreateScreenTurnedOnAtomMatcher();
+
+// Create AtomMatcher proto for screen turned off.
+AtomMatcher CreateScreenTurnedOffAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned on.
+AtomMatcher CreateSyncStartAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned off.
+AtomMatcher CreateSyncEndAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to background.
+AtomMatcher CreateMoveToBackgroundAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to foreground.
+AtomMatcher CreateMoveToForegroundAtomMatcher();
+
+// Create Predicate proto for screen is off.
+Predicate CreateScreenIsOffPredicate();
+
+// Create Predicate proto for a running scheduled job.
+Predicate CreateScheduledJobPredicate();
+
+// Create Predicate proto for holding wakelock.
+Predicate CreateHoldingWakelockPredicate();
+
+// Create a Predicate proto for app syncing.
+Predicate CreateIsSyncingPredicate();
+
+// Create a Predicate proto for app is in background.
+Predicate CreateIsInBackgroundPredicate();
+
+// Add a predicate to the predicate combination.
+void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
+
+// Create dimensions from primitive fields.
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
+
+// Create dimensions by attribution uid and tag.
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+                                                  const std::vector<Position>& positions);
+
+// Create dimensions by attribution uid only.
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+                                            const std::vector<Position>& positions);
+
+// 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);
+
+// Create log event when scheduled job starts.
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+    const std::vector<AttributionNodeInternal>& attributions,
+    const string& name, uint64_t timestampNs);
+
+// 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);
+
+// Create log event when the app sync starts.
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+        const std::vector<AttributionNodeInternal>& attributions, const string& name,
+        uint64_t timestampNs);
+
+// 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);
+
+// Create a statsd log event processor upon the start time in seconds, config and key.
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+                                              const ConfigKey& key);
+
+// Util function to sort the log events by timestamp.
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
+
+int64_t StringToId(const string& str);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index b541612..0c9b701 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -205,6 +205,29 @@
     }
 }
 
+bool equalDimensions(const std::vector<Matcher>& dimension_a,
+                     const std::vector<Matcher>& dimension_b) {
+    bool eq = dimension_a.size() == dimension_b.size();
+    for (size_t i = 0; eq && i < dimension_a.size(); ++i) {
+        if (dimension_b[i] != dimension_a[i]) {
+            eq = false;
+        }
+    }
+    return eq;
+}
+
+bool HasPositionANY(const FieldMatcher& matcher) {
+    if (matcher.has_position() && matcher.position() == Position::ANY) {
+        return true;
+    }
+    for (const auto& child : matcher.child()) {
+        if (HasPositionANY(child)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 621d0be9..0e3ae06 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -336,11 +336,16 @@
     Value mValue;
 };
 
+bool HasPositionANY(const FieldMatcher& matcher);
+
 bool isAttributionUidField(const FieldValue& value);
 
 void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
 
 bool isAttributionUidField(const Field& field, const Value& value);
+
+bool equalDimensions(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 cc706313..d0c8311 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -59,6 +59,33 @@
     return JenkinsHashWhiten(hash);
 }
 
+bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
+                  HashableDimensionKey* output) {
+    for (size_t i = 0; i < matcherFields.size(); ++i) {
+        const auto& matcher = matcherFields[i];
+        bool found = false;
+        for (const auto& value : values) {
+            // TODO: potential optimization here to break early because all fields are naturally
+            // sorted.
+            if (value.mField.matches(matcher)) {
+                output->addValue(value);
+                output->mutableValue(i)->mField.setTag(value.mField.getTag());
+                output->mutableValue(i)->mField.setField(value.mField.getField() & matcher.mMask);
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) {
+            VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
+                   matcher.mMatcher.getField());
+            return false;
+        }
+    }
+
+    return true;
+}
+
 // Filter fields using the matchers and output the results as a HashableDimensionKey.
 // Note: HashableDimensionKey is just a wrapper for vector<FieldValue>
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
@@ -168,22 +195,21 @@
 
 void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
                               const Metric2Condition& links,
-                              vector<HashableDimensionKey>* conditionDimension) {
+                              HashableDimensionKey* conditionDimension) {
     // Get the dimension first by using dimension from what.
     filterValues(links.metricFields, eventValues, conditionDimension);
 
-    // Then replace the field with the dimension from condition.
-    for (auto& dim : *conditionDimension) {
-        size_t count = dim.getValues().size();
-        if (count != links.conditionFields.size()) {
-            // ALOGE("WTF condition link is bad");
-            return;
-        }
+    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++) {
-            dim.mutableValue(i)->mField.setField(links.conditionFields[i].mMatcher.getField());
-            dim.mutableValue(i)->mField.setTag(links.conditionFields[i].mMatcher.getTag());
-        }
+    for (size_t i = 0; i < count; i++) {
+        conditionDimension->mutableValue(i)->mField.setField(
+            links.conditionFields[i].mMatcher.getField());
+        conditionDimension->mutableValue(i)->mField.setTag(
+            links.conditionFields[i].mMatcher.getTag());
     }
 }
 
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 57bdf68..4cfed88 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -65,10 +65,6 @@
 
     std::string toString() const;
 
-    inline const char* c_str() const {
-        return toString().c_str();
-    }
-
     bool operator==(const HashableDimensionKey& that) const;
 
     bool operator<(const HashableDimensionKey& that) const;
@@ -104,6 +100,10 @@
         return mDimensionKeyInCondition;
     }
 
+    inline void setDimensionKeyInCondition(const HashableDimensionKey& key) {
+        mDimensionKeyInCondition = key;
+    }
+
     bool hasDimensionKeyInCondition() const {
         return mDimensionKeyInCondition.getValues().size() > 0;
     }
@@ -112,9 +112,6 @@
 
     bool operator<(const MetricDimensionKey& that) const;
 
-    inline const char* c_str() const {
-        return toString().c_str();
-    }
   private:
       HashableDimensionKey mDimensionKeyInWhat;
       HashableDimensionKey mDimensionKeyInCondition;
@@ -134,6 +131,9 @@
  */
 bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
                   std::vector<HashableDimensionKey>* output);
+// This function is used when there is at most one output dimension key. (no ANY matcher)
+bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
+                  HashableDimensionKey* output);
 
 /**
  * Filter the values from FieldValues using the matchers.
@@ -146,7 +146,7 @@
 
 void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
                               const Metric2Condition& links,
-                              std::vector<HashableDimensionKey>* conditionDimension);
+                              HashableDimensionKey* conditionDimension);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index a672ab4..8db8200 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -123,10 +123,21 @@
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
+    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);
+
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e0a1299..30896e6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -177,11 +177,6 @@
     /**
      * Print the event log.
      */
-    status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
-
-    /**
-     * Print the event log.
-     */
     status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
 
     /**
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 13a2b7b..3661d2b 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -91,6 +91,9 @@
 
         if (allConditionTrackers[childIndex]->isSliced()) {
             setSliced(true);
+            mSlicedChildren.push_back(childIndex);
+        } else {
+            mUnSlicedChildren.push_back(childIndex);
         }
         mChildren.push_back(childIndex);
         mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
@@ -107,13 +110,19 @@
 
 void CombinationConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const std::vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache,
+        const std::vector<Matcher>& dimensionFields,
+        const bool isSubOutputDimensionFields,
+        const bool isPartialLink,
+        vector<ConditionState>& conditionCache,
         std::unordered_set<HashableDimensionKey>& dimensionsKeySet) 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, conditionCache,
+                                                      dimensionFields,
+                                                      isSubOutputDimensionFields,
+                                                      isPartialLink,
+                                                      conditionCache,
                                                       dimensionsKeySet);
         }
     }
@@ -150,7 +159,11 @@
         nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
 
         conditionChangedCache[mIndex] = nonSlicedChanged;
+        mUnSlicedPart = newCondition;
     } else {
+        mUnSlicedPart = evaluateCombinationCondition(
+            mUnSlicedChildren, mLogicalOperation, nonSlicedConditionCache);
+
         for (const int childIndex : mChildren) {
             // If any of the sliced condition in children condition changes, the combination
             // condition may be changed too.
@@ -168,13 +181,14 @@
 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, dimensionsKeySet);
+                allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
     }
     evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
     if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) {
@@ -183,6 +197,18 @@
     return conditionCache[mIndex];
 }
 
+bool CombinationConditionTracker::equalOutputDimensions(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const vector<Matcher>& dimensions) const {
+    if (mSlicedChildren.size() != 1 ||
+        mSlicedChildren.front() >= (int)allConditions.size() ||
+        mLogicalOperation != LogicalOperation::AND) {
+        return false;
+    }
+    const sp<ConditionTracker>& slicedChild = allConditions.at(mSlicedChildren.front());
+    return slicedChild->equalOutputDimensions(allConditions, dimensions);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 7b8dc6b..481cb20 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -44,12 +44,15 @@
     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;
 
     // Only one child predicate can have dimension.
@@ -63,6 +66,7 @@
         }
         return nullptr;
     }
+
     // Only one child predicate can have dimension.
     const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
             const std::vector<sp<ConditionTracker>>& allConditions) const override {
@@ -75,6 +79,26 @@
         return nullptr;
     }
 
+    bool IsSimpleCondition() const  override { return false; }
+
+    bool IsChangedDimensionTrackable() const  override {
+        return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1;
+    }
+
+    bool equalOutputDimensions(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const vector<Matcher>& dimensions) const override;
+
+    void getTrueSlicedDimensions(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            std::set<HashableDimensionKey>* dimensions) const override {
+        if (mSlicedChildren.size() == 1) {
+            return allConditions[mSlicedChildren.front()]->getTrueSlicedDimensions(
+                allConditions, dimensions);
+        }
+    }
+
+
 private:
     LogicalOperation mLogicalOperation;
 
@@ -83,6 +107,10 @@
     // map the name to object. We don't want to store smart pointers to children, because it
     // increases the risk of circular dependency and memory leak.
     std::vector<int> mChildren;
+
+    std::vector<int> mSlicedChildren;
+    std::vector<int> mUnSlicedChildren;
+
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 856a3a0..1f4266b 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -84,18 +84,28 @@
     //                       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, std::vector<ConditionState>& conditionCache,
+            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;
 
     // return the list of LogMatchingTracker index that this ConditionTracker uses.
@@ -107,7 +117,7 @@
         mSliced = mSliced | sliced;
     }
 
-    bool isSliced() const {
+    inline bool isSliced() const {
         return mSliced;
     }
 
@@ -116,6 +126,26 @@
     virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
             const std::vector<sp<ConditionTracker>>& allConditions) const = 0;
 
+    inline int64_t getConditionId() const {
+        return mConditionId;
+    }
+
+    virtual void getTrueSlicedDimensions(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        std::set<HashableDimensionKey>* dimensions) const = 0;
+
+    virtual bool IsChangedDimensionTrackable() const = 0;
+
+    virtual bool IsSimpleCondition() const = 0;
+
+    virtual bool equalOutputDimensions(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const vector<Matcher>& dimensions) const = 0;
+
+    inline ConditionState getUnSlicedPartConditionState() const  {
+        return mUnSlicedPart;
+    }
+
 protected:
     const int64_t mConditionId;
 
@@ -131,6 +161,7 @@
     ConditionState mNonSlicedConditionState;
 
     bool mSliced;
+    ConditionState mUnSlicedPart;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index 952b0cc..23a9d37 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -26,19 +26,24 @@
 
 ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters,
                                       const vector<Matcher>& dimensionFields,
+                                      const bool isSubOutputDimensionFields,
+                                      const bool isPartialLink,
                                       std::unordered_set<HashableDimensionKey>* dimensionKeySet) {
     vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
 
     mAllConditions[index]->isConditionMet(
-        parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet);
+        parameters, mAllConditions, dimensionFields, isSubOutputDimensionFields, isPartialLink,
+        cache, *dimensionKeySet);
     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,
-                                 *dimensionsKeySet);
+                                                           isSubOutputDimensionFields,
+                                                           *dimensionsKeySet);
 }
 
 const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions(
@@ -51,6 +56,30 @@
     return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions);
 }
 
+bool ConditionWizard::IsChangedDimensionTrackable(const int index) {
+    if (index >= 0 && index < (int)mAllConditions.size()) {
+        return mAllConditions[index]->IsChangedDimensionTrackable();
+    } else {
+        return false;
+    }
+}
+
+bool ConditionWizard::IsSimpleCondition(const int index) {
+    if (index >= 0 && index < (int)mAllConditions.size()) {
+        return mAllConditions[index]->IsSimpleCondition();
+    } else {
+        return false;
+    }
+}
+
+bool ConditionWizard::equalOutputDimensions(const int index, const vector<Matcher>& dimensions) {
+    if (index >= 0 && index < (int)mAllConditions.size()) {
+        return mAllConditions[index]->equalOutputDimensions(mAllConditions, dimensions);
+    } else {
+        return false;
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index fcfdc2a..a6f88af 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -41,15 +41,30 @@
     // 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;
 
     virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const;
     virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
             const int index) const;
+    bool equalOutputDimensions(const int index, const vector<Matcher>& dimensions);
+
+    bool IsChangedDimensionTrackable(const int index);
+    bool IsSimpleCondition(const int index);
+
+    ConditionState getUnSlicedPartConditionState(const int index) {
+        return mAllConditions[index]->getUnSlicedPartConditionState();
+    }
+    void getTrueSlicedDimensions(const int index,
+        std::set<HashableDimensionKey>* trueDimensions) const {
+        return mAllConditions[index]->getTrueSlicedDimensions(mAllConditions, trueDimensions);
+    }
 
 private:
     std::vector<sp<ConditionTracker>> mAllConditions;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 9e27a8b..4913aef 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -34,7 +34,7 @@
         const ConfigKey& key, const int64_t& id, const int index,
         const SimplePredicate& simplePredicate,
         const unordered_map<int64_t, int>& trackerNameIndexMap)
-    : ConditionTracker(id, index), mConfigKey(key) {
+    : ConditionTracker(id, index), mConfigKey(key), mContainANYPositionInInternalDimensions(false) {
     VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
     mCountNesting = simplePredicate.count_nesting();
 
@@ -80,6 +80,7 @@
             mSliced = true;
             mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
         }
+        mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions());
     }
 
     if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
@@ -90,6 +91,10 @@
 
     mNonSlicedConditionState = mInitialValue;
 
+    if (!mSliced) {
+        mUnSlicedPart = mInitialValue;
+    }
+
     mInitialized = true;
 }
 
@@ -109,7 +114,7 @@
 void SimpleConditionTracker::dumpState() {
     VLOG("%lld DUMP:", (long long)mConditionId);
     for (const auto& pair : mSlicedConditionState) {
-        VLOG("\t%s : %d", pair.first.c_str(), pair.second);
+        VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second);
     }
 
     VLOG("Changed to true keys: \n");
@@ -140,6 +145,9 @@
     mInitialValue = ConditionState::kFalse;
     mSlicedConditionState.clear();
     conditionCache[mIndex] = ConditionState::kFalse;
+    if (!mSliced) {
+        mUnSlicedPart = ConditionState::kFalse;
+    }
 }
 
 bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -154,7 +162,7 @@
         // 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.c_str());
+                (long long)mConditionId, newKey.toString().c_str());
             return true;
         }
     }
@@ -177,13 +185,13 @@
         // We get a new output key.
         newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
         if (matchStart && mInitialValue != ConditionState::kTrue) {
-            mSlicedConditionState.insert(std::make_pair(outputKey, 1));
+            mSlicedConditionState[outputKey] = 1;
             changed = true;
             mLastChangedToTrueDimensions.insert(outputKey);
         } else if (mInitialValue != ConditionState::kFalse) {
             // it's a stop and we don't have history about it.
             // If the default condition is not false, it means this stop is valuable to us.
-            mSlicedConditionState.insert(std::make_pair(outputKey, 0));
+            mSlicedConditionState[outputKey] = 0;
             mLastChangedToFalseDimensions.insert(outputKey);
             changed = true;
         }
@@ -226,7 +234,7 @@
             // if default condition is false, it means we don't need to keep the false values.
             if (mInitialValue == ConditionState::kFalse && startedCount == 0) {
                 mSlicedConditionState.erase(outputIt);
-                VLOG("erase key %s", outputKey.c_str());
+                VLOG("erase key %s", outputKey.toString().c_str());
             }
         }
     }
@@ -238,6 +246,7 @@
 
     (*conditionChangedCache) = changed;
     (*conditionCache) = newCondition;
+
     VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
          conditionChangedCache[mIndex] == true);
 }
@@ -294,6 +303,7 @@
                 conditionCache[mIndex] =
                         itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
             }
+            mUnSlicedPart = conditionCache[mIndex];
         }
 
         return;
@@ -305,6 +315,17 @@
     if (mOutputDimensions.size() == 0) {
         handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState,
                              &overallChanged);
+    } else if (!mContainANYPositionInInternalDimensions) {
+        HashableDimensionKey outputValue;
+        filterValues(mOutputDimensions, event.getValues(), &outputValue);
+
+        // If this event has multiple nodes in the attribution chain,  this log event probably will
+        // generate multiple dimensions. If so, we will find if the condition changes for any
+        // dimension and ask the corresponding metric producer to verify whether the actual sliced
+        // condition has changed or not.
+        // A high level assumption is that a predicate is either sliced or unsliced. We will never
+        // have both sliced and unsliced version of a predicate.
+        handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged);
     } else {
         std::vector<HashableDimensionKey> outputValues;
         filterValues(mOutputDimensions, event.getValues(), &outputValues);
@@ -328,11 +349,17 @@
     }
     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, vector<ConditionState>& conditionCache,
+        const vector<Matcher>& dimensionFields,
+        const bool isSubOutputDimensionFields,
+        const bool isPartialLink,
+        vector<ConditionState>& conditionCache,
         std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
 
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
@@ -347,7 +374,7 @@
         ConditionState conditionState = ConditionState::kNotEvaluated;
         if (dimensionFields.size() > 0 && dimensionFields[0].mMatcher.getTag() == mDimensionTag) {
             conditionState = conditionState | getMetConditionDimension(
-                allConditions, dimensionFields, dimensionsKeySet);
+                allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
         } else {
             conditionState = conditionState | mInitialValue;
             if (!mSliced) {
@@ -362,42 +389,48 @@
         conditionCache[mIndex] = conditionState;
         return;
     }
-    std::vector<HashableDimensionKey> defaultKeys = { DEFAULT_DIMENSION_KEY };
-    const std::vector<HashableDimensionKey> &keys =
-            (pair == conditionParameters.end()) ? defaultKeys : pair->second;
 
     ConditionState conditionState = ConditionState::kNotEvaluated;
-    for (size_t i = 0; i < keys.size(); ++i) {
-        const HashableDimensionKey& key = keys[i];
+    const HashableDimensionKey& key = pair->second;
+    if (isPartialLink) {
+        // For unseen key, check whether the require dimensions are subset of sliced condition
+        // output.
+        conditionState = conditionState | mInitialValue;
+        for (const auto& slice : mSlicedConditionState) {
+            ConditionState sliceState =
+                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 {
         auto startedCountIt = mSlicedConditionState.find(key);
+        conditionState = conditionState | mInitialValue;
         if (startedCountIt != mSlicedConditionState.end()) {
             ConditionState sliceState =
                 startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
             conditionState = conditionState | sliceState;
             if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-                vector<HashableDimensionKey> dimensionKeys;
-                filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKeys);
-                dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
-            }
-        } else {
-            // For unseen key, check whether the require dimensions are subset of sliced condition
-            // output.
-            conditionState = conditionState | mInitialValue;
-            for (const auto& slice : mSlicedConditionState) {
-                ConditionState sliceState =
-                    slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-                if (slice.first.contains(key)) {
-                    conditionState = conditionState | sliceState;
-                    if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-                        vector<HashableDimensionKey> dimensionKeys;
-                        filterValues(dimensionFields, slice.first.getValues(), &dimensionKeys);
-
-                        dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
-                    }
-                    }
+                if (isSubOutputDimensionFields) {
+                    HashableDimensionKey dimensionKey;
+                    filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKey);
+                    dimensionsKeySet.insert(dimensionKey);
+                } else {
+                    dimensionsKeySet.insert(startedCountIt->first);
                 }
             }
         }
+
+    }
     conditionCache[mIndex] = conditionState;
     VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
 }
@@ -405,6 +438,7 @@
 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 ||
@@ -424,10 +458,13 @@
         conditionState = conditionState | sliceState;
 
         if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
-            vector<HashableDimensionKey> dimensionKeys;
-            filterValues(dimensionFields, slice.first.getValues(), &dimensionKeys);
-
-            dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
+            if (isSubOutputDimensionFields) {
+                HashableDimensionKey dimensionKey;
+                filterValues(dimensionFields, slice.first.getValues(), &dimensionKey);
+                dimensionsKeySet.insert(dimensionKey);
+            } else {
+                dimensionsKeySet.insert(slice.first);
+            }
         }
     }
     return conditionState;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index e4b72b8..47d1ece 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -49,12 +49,15 @@
     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;
 
     virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
@@ -65,6 +68,7 @@
             return nullptr;
         }
     }
+
     virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
             const std::vector<sp<ConditionTracker>>& allConditions) const {
         if (mSliced) {
@@ -74,6 +78,26 @@
         }
     }
 
+    void getTrueSlicedDimensions(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            std::set<HashableDimensionKey>* dimensions) const override {
+        for (const auto& itr : mSlicedConditionState) {
+            if (itr.second > 0) {
+                dimensions->insert(itr.first);
+            }
+        }
+    }
+
+    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);
+    }
+
 private:
     const ConfigKey mConfigKey;
     // The index of the LogEventMatcher which defines the start.
@@ -92,6 +116,8 @@
 
     std::vector<Matcher> mOutputDimensions;
 
+    bool mContainANYPositionInInternalDimensions;
+
     std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
     std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
 
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp
index e479f93..c68875c 100644
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ b/cmds/statsd/src/condition/StateTracker.cpp
@@ -107,7 +107,7 @@
         // 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.c_str());
+                (long long)mConditionId, newKey.toString().c_str());
             return true;
         }
     }
@@ -181,7 +181,10 @@
 
 void StateTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache,
+        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.
@@ -203,20 +206,19 @@
         return;
     }
 
-    const auto& primaryKeys = pair->second;
+    const auto& primaryKey = pair->second;
     conditionCache[mIndex] = mInitialValue;
-    for (const auto& primaryKey : primaryKeys) {
-        auto it = mSlicedState.find(primaryKey);
-        if (it != mSlicedState.end()) {
-            conditionCache[mIndex] = ConditionState::kTrue;
-            dimensionsKeySet.insert(it->second);
-        }
+    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) {
diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h
index 3fe6e60..2bdf98c 100644
--- a/cmds/statsd/src/condition/StateTracker.h
+++ b/cmds/statsd/src/condition/StateTracker.h
@@ -56,6 +56,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;
 
@@ -67,6 +69,7 @@
     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(
@@ -79,6 +82,24 @@
         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;
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index da5ef44..22b2a30 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -71,6 +71,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()) {
@@ -113,7 +114,7 @@
 
     for (const auto& counter : mPastBuckets) {
         const MetricDimensionKey& dimensionKey = counter.first;
-        VLOG("  dimension key %s", dimensionKey.c_str());
+        VLOG("  dimension key %s", dimensionKey.toString().c_str());
 
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -176,7 +177,7 @@
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("CountMetric %lld dropping data for dimension key %s",
-                (long long)mMetricId, newKey.c_str());
+                (long long)mMetricId, newKey.toString().c_str());
             return true;
         }
     }
@@ -218,7 +219,7 @@
                                          countWholeBucket);
     }
 
-    VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
+    VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(),
          (long long)(*mCurrentSlicedCounter)[eventKey]);
 }
 
@@ -253,7 +254,8 @@
         info.mCount = counter.second;
         auto& bucketList = mPastBuckets[counter.first];
         bucketList.push_back(info);
-        VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, counter.first.c_str(),
+        VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId,
+             counter.first.toString().c_str(),
              (long long)counter.second);
     }
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 29a892d..0dd3f70 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -67,7 +67,8 @@
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
       mStopAllIndex(stopAllIndex),
-      mNested(nesting) {
+      mNested(nesting),
+      mContainANYPositionInInternalDimensions(false) {
     // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
     // them in the base class, because the proto generated CountMetric, and DurationMetric are
     // not related. Maybe we should add a template in the future??
@@ -80,10 +81,12 @@
 
     if (metric.has_dimensions_in_what()) {
         translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+        mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
     if (internalDimensions.has_field()) {
         translateFieldMatcher(internalDimensions, &mInternalDimensions);
+        mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions);
     }
 
     if (metric.has_dimensions_in_condition()) {
@@ -100,19 +103,18 @@
         }
     }
     mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+    mUnSlicedPartCondition = ConditionState::kUnknown;
 
-    if (mDimensionsInWhat.size() == mInternalDimensions.size()) {
-        bool mUseWhatDimensionAsInternalDimension = true;
-        for (size_t i = 0; mUseWhatDimensionAsInternalDimension &&
-            i < mDimensionsInWhat.size(); ++i) {
-            if (mDimensionsInWhat[i] != mInternalDimensions[i]) {
-                mUseWhatDimensionAsInternalDimension = false;
-            }
+    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);
         }
-    } else {
-        mUseWhatDimensionAsInternalDimension = false;
     }
-
     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
@@ -139,12 +141,171 @@
             return make_unique<OringDurationTracker>(
                     mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
                     mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+                    mStartTimeNs, mBucketSizeNs, mConditionSliced,
+                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
                     mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
                     mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+                    mStartTimeNs, 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.
+void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(const uint64_t eventTime) {
+    if (mMetric2ConditionLinks.size() != 1 ||
+        !mHasLinksToAllConditionDimensionsInTracker ||
+        !mDimensionsInCondition.empty()) {
+        return;
+    }
+
+    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;
+    }
+
+    auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex);
+    auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex);
+
+    // The condition change is from the unsliced predicates.
+    // We need to find out the true dimensions from the sliced predicate and flip their condition
+    // state based on the new unsliced condition state.
+    if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
+        (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
+        std::set<HashableDimensionKey> trueConditionDimensions;
+        mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions);
+        for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+            HashableDimensionKey linkedConditionDimensionKey;
+            getDimensionForCondition(whatIt.first.getValues(),
+                                     mMetric2ConditionLinks[0],
+                                     &linkedConditionDimensionKey);
+            if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
+                    trueConditionDimensions.end()) {
+                for (auto& condIt : whatIt.second) {
+                    condIt.second->onConditionChanged(
+                        currentUnSlicedPartCondition, eventTime);
+                }
+            }
+        }
+    } else {
+        // Handle the condition change from the sliced predicate.
+        if (currentUnSlicedPartCondition) {
+            for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+                HashableDimensionKey linkedConditionDimensionKey;
+                getDimensionForCondition(whatIt.first.getValues(),
+                                         mMetric2ConditionLinks[0],
+                                         &linkedConditionDimensionKey);
+                if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
+                        dimensionsChangedToTrue->end()) {
+                    for (auto& condIt : whatIt.second) {
+                        condIt.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(const uint64_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()) {
+                            unique_ptr<DurationTracker> newTracker =
+                                whatIt.second.begin()->second->clone(eventTime);
+                            if (newTracker != nullptr) {
+                                newTracker->setEventKey(
+                                    MetricDimensionKey(whatIt.first, trueDim));
+                                newTracker->onConditionChanged(true, eventTime);
+                                whatIt.second[trueDim] = std::move(newTracker);
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
 }
 
@@ -152,6 +313,23 @@
     VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
     flushIfNeededLocked(eventTime);
 
+    if (!mConditionSliced) {
+        return;
+    }
+
+    bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex);
+    if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker &&
+        mDimensionsInCondition.empty()) {
+        onSlicedConditionMayChangeLocked_opt1(eventTime);
+        return;
+    }
+
+    if (changeDimTrackable && mSameConditionDimensionsInTracker &&
+        mMetric2ConditionLinks.size() <= 1) {
+        onSlicedConditionMayChangeLocked_opt2(eventTime);
+        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) {
@@ -166,6 +344,7 @@
     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) {
@@ -177,9 +356,12 @@
                 if (!whatIt.second.empty()) {
                     unique_ptr<DurationTracker> newTracker =
                         whatIt.second.begin()->second->clone(eventTime);
-                    newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
-                    newTracker->onSlicedConditionMayChange(eventTime);
-                    whatIt.second[conditionDimension] = std::move(newTracker);
+                    if (newTracker != nullptr) {
+                        newTracker->setEventKey(MetricDimensionKey(
+                                whatIt.first, conditionDimension));
+                        newTracker->onSlicedConditionMayChange(eventTime);
+                        whatIt.second[conditionDimension] = std::move(newTracker);
+                    }
                 }
             }
         }
@@ -192,15 +374,20 @@
             }
             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 newTracker = whatIt.second.begin()->second->clone(eventTime);
-                    newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
-                    newTracker->onSlicedConditionMayChange(eventTime);
-                    whatIt.second[conditionDimension] = std::move(newTracker);
+                    if (newTracker != nullptr) {
+                        newTracker->setEventKey(
+                            MetricDimensionKey(whatIt.first, conditionDimension));
+                        newTracker->onSlicedConditionMayChange(eventTime);
+                        whatIt.second[conditionDimension] = std::move(newTracker);
+                    }
                 }
             }
         }
@@ -241,7 +428,7 @@
 
     for (const auto& pair : mPastBuckets) {
         const MetricDimensionKey& dimensionKey = pair.first;
-        VLOG("  dimension key %s", dimensionKey.c_str());
+        VLOG("  dimension key %s", dimensionKey.toString().c_str());
 
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -291,7 +478,8 @@
             whatIt != mCurrentSlicedDurationTrackerMap.end();) {
         for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
             if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
-                VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+                VLOG("erase bucket for key %s %s",
+                     whatIt->first.toString().c_str(), it->first.toString().c_str());
                 it = whatIt->second.erase(it);
             } else {
                 ++it;
@@ -314,7 +502,8 @@
             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.c_str(), it->first.c_str());
+                VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
+                     it->first.toString().c_str());
                 it = whatIt->second.erase(it);
             } else {
                 ++it;
@@ -338,7 +527,8 @@
     if (verbose) {
         for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
             for (const auto& slice : whatIt.second) {
-                fprintf(out, "\t%s\t%s\n", whatIt.first.c_str(), slice.first.c_str());
+                fprintf(out, "\t(what)%s\t(condition)%s\n", whatIt.first.toString().c_str(),
+                        slice.first.toString().c_str());
                 slice.second->dumpStates(out, verbose);
             }
         }
@@ -353,7 +543,7 @@
         // 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 dimension key %s",
-                (long long)mMetricId, newKey.c_str());
+                (long long)mMetricId, newKey.toString().c_str());
             return true;
         }
     }
@@ -388,14 +578,21 @@
         return;
     }
 
-    std::vector<HashableDimensionKey> values;
-    filterValues(mInternalDimensions, event.getValues(), &values);
-    if (values.empty()) {
+    if (mInternalDimensions.empty()) {
         it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
                               event.GetElapsedTimestampNs(), conditionKeys);
     } else {
-        for (const auto& value : values) {
-            it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
+        if (mContainANYPositionInInternalDimensions) {
+            std::vector<HashableDimensionKey> dimensionKeys;
+            filterValues(mInternalDimensions, event.getValues(), &dimensionKeys);
+            for (const auto& key : dimensionKeys) {
+                it->second->noteStart(key, condition, event.GetElapsedTimestampNs(), conditionKeys);
+            }
+        } else {
+            HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
+            filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
+            it->second->noteStart(
+                dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
         }
     }
 
@@ -408,8 +605,113 @@
     ALOGW("Not used in duration tracker.");
 }
 
+void DurationMetricProducer::onMatchedLogEventLocked_simple(const size_t matcherIndex,
+                                                            const LogEvent& event) {
+    uint64_t eventTimeNs = event.GetElapsedTimestampNs();
+    if (eventTimeNs < mStartTimeNs) {
+        return;
+    }
+
+    flushIfNeededLocked(event.GetElapsedTimestampNs());
+
+    // Handles Stopall events.
+    if (matcherIndex == mStopAllIndex) {
+        for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+            for (auto& pair : whatIt.second) {
+                pair.second->noteStopAll(event.GetElapsedTimestampNs());
+            }
+        }
+        return;
+    }
+
+    HashableDimensionKey dimensionInWhat;
+    if (!mDimensionsInWhat.empty()) {
+        filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+    } else {
+       dimensionInWhat = DEFAULT_DIMENSION_KEY;
+    }
+
+    // Handles Stop events.
+    if (matcherIndex == mStopIndex) {
+        if (mUseWhatDimensionAsInternalDimension) {
+            auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
+            if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+                for (const auto& condIt : whatIt->second) {
+                    condIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
+                }
+            }
+            return;
+        }
+
+        HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
+        if (!mInternalDimensions.empty()) {
+            filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
+        }
+
+        auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
+        if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+            for (const auto& condIt : whatIt->second) {
+                condIt.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);
+        condition = (conditionState == ConditionState::kTrue);
+        if (mDimensionsInCondition.empty() && condition) {
+            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+        }
+    } else {
+        condition = mCondition;
+        if (condition) {
+            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+        }
+    }
+
+    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();
+                handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first),
+                    conditionKey, cond, event);
+                dimensionKeysInCondition.erase(condIt.first);
+            }
+        }
+        for (const auto& conditionDimension : dimensionKeysInCondition) {
+            handleStartEvent(MetricDimensionKey(dimensionInWhat, conditionDimension), conditionKey,
+                             condition, event);
+        }
+    }
+}
+
 void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
                                                      const LogEvent& event) {
+    if (!mContainANYPositionInDimensionsInWhat) {
+        onMatchedLogEventLocked_simple(matcherIndex, event);
+        return;
+    }
+
     uint64_t eventTimeNs = event.GetElapsedTimestampNs();
     if (eventTimeNs < mStartTimeNs) {
         return;
@@ -448,19 +750,17 @@
             return;
         }
 
-        std::vector<HashableDimensionKey> internalDimensionKeys;
-        filterValues(mInternalDimensions, event.getValues(), &internalDimensionKeys);
-        if (internalDimensionKeys.empty()) {
-            internalDimensionKeys.push_back(DEFAULT_DIMENSION_KEY);
+        HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
+        if (!mInternalDimensions.empty()) {
+            filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
         }
+
         for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
             auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
             if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
                 for (const auto& condIt : whatIt->second) {
-                    for (const auto& internalDimensionKey : internalDimensionKeys) {
-                        condIt.second->noteStop(
-                            internalDimensionKey, event.GetElapsedTimestampNs(), false);
-                    }
+                    condIt.second->noteStop(
+                        internalDimensionKey, event.GetElapsedTimestampNs(), false);
                 }
             }
         }
@@ -477,6 +777,8 @@
 
         auto conditionState =
             mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+                           !mSameConditionDimensionsInTracker,
+                           !mHasLinksToAllConditionDimensionsInTracker,
                            &dimensionKeysInCondition);
         condition = (conditionState == ConditionState::kTrue);
         if (mDimensionsInCondition.empty() && condition) {
@@ -490,32 +792,30 @@
     }
 
     for (const auto& whatDimension : dimensionInWhatValues) {
-        auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
-        // 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();
-                handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
-                                 conditionKey, cond, event);
-            }
+        if (dimensionKeysInCondition.empty()) {
+            handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+                             conditionKey, condition, event);
         } else {
-            // If it is a new what dimension key, we need to handle the start events for all current
-            // condition dimensions.
+            auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+            // 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();
+                    handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
+                                     conditionKey, cond, event);
+                    dimensionKeysInCondition.erase(condIt.first);
+                }
+            }
             for (const auto& conditionDimension : dimensionKeysInCondition) {
                 handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
                                  conditionKey, condition, event);
             }
         }
-        if (dimensionKeysInCondition.empty()) {
-            handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
-                             conditionKey, condition, event);
-        }
     }
 }
 
-
 size_t DurationMetricProducer::byteSizeLocked() const {
     size_t totalSize = 0;
     for (const auto& pair : mPastBuckets) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 23408a7..6746e11 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -51,6 +51,9 @@
 
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
+
+    void onMatchedLogEventLocked_simple(const size_t matcherIndex, const LogEvent& event);
+
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKeys, bool condition,
@@ -69,6 +72,9 @@
     // Internal interface to handle sliced condition change.
     void onSlicedConditionMayChangeLocked(const uint64_t eventTime) override;
 
+    void onSlicedConditionMayChangeLocked_opt1(const uint64_t eventTime);
+    void onSlicedConditionMayChangeLocked_opt2(const uint64_t eventTime);
+
     // Internal function to calculate the current used bytes.
     size_t byteSizeLocked() const override;
 
@@ -98,9 +104,14 @@
     // The dimension from the atom predicate. e.g., uid, wakelock name.
     vector<Matcher> mInternalDimensions;
 
+    bool mContainANYPositionInInternalDimensions;
+
     // This boolean is true iff When mInternalDimensions == mDimensionsInWhat
     bool mUseWhatDimensionAsInternalDimension;
 
+    // Caches the current unsliced part condition.
+    ConditionState mUnSlicedPartCondition;
+
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     // TODO: Add a lock to mPastBuckets.
     std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 288f563c..e479e5c 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -83,6 +83,7 @@
     // TODO: use UidMap if uid->pkg_name is required
     if (metric.has_dimensions_in_what()) {
         translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+        mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
     if (metric.has_dimensions_in_condition()) {
@@ -140,7 +141,7 @@
     for (const auto& pair : mPastBuckets) {
         const MetricDimensionKey& dimensionKey = pair.first;
 
-        VLOG("  dimension key %s", dimensionKey.c_str());
+        VLOG("  dimension key %s", dimensionKey.toString().c_str());
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
@@ -283,7 +284,7 @@
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("GaugeMetric %lld dropping data for dimension key %s",
-                (long long)mMetricId, newKey.c_str());
+                (long long)mMetricId, newKey.toString().c_str());
             return true;
         }
     }
@@ -398,7 +399,8 @@
         info.mGaugeAtoms = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId, slice.first.c_str());
+        VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId,
+             slice.first.toString().c_str());
     }
 
     // If we have anomaly trackers, we need to update the partial bucket values.
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 18694a1..6c90b03 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -39,9 +39,10 @@
         for (const auto& link : mMetric2ConditionLinks) {
             getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
         }
-
         auto conditionState =
             mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+                           !mSameConditionDimensionsInTracker,
+                           !mHasLinksToAllConditionDimensionsInTracker,
                            &dimensionKeysInCondition);
         condition = (conditionState == ConditionState::kTrue);
     } else {
@@ -52,25 +53,41 @@
         dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
     }
 
-    vector<HashableDimensionKey> dimensionInWhatValues;
-    if (!mDimensionsInWhat.empty()) {
-        filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
-    } else {
-        dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
-    }
+    if (mContainANYPositionInDimensionsInWhat) {
+        vector<HashableDimensionKey> dimensionInWhatValues;
+        if (!mDimensionsInWhat.empty()) {
+            filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+        } else {
+            dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+        }
 
-    for (const auto& whatDimension : dimensionInWhatValues) {
+        for (const auto& whatDimension : dimensionInWhatValues) {
+            for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+                onMatchedLogEventInternalLocked(
+                        matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
+                        conditionKey, condition, event);
+            }
+            if (dimensionKeysInCondition.empty()) {
+                onMatchedLogEventInternalLocked(
+                        matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+                         conditionKey, condition, event);
+            }
+        }
+    } else {
+        HashableDimensionKey dimensionInWhat;
+        filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+        MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
         for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+            metricKey.setDimensionKeyInCondition(conditionDimensionKey);
             onMatchedLogEventInternalLocked(
-                    matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
-                    conditionKey, condition, event);
+                    matcherIndex, metricKey, conditionKey, condition, event);
         }
         if (dimensionKeysInCondition.empty()) {
             onMatchedLogEventInternalLocked(
-                    matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
-                     conditionKey, condition, event);
+                    matcherIndex, metricKey, conditionKey, condition, event);
         }
     }
+
  }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 05b7f87..ea45f43 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -50,7 +50,11 @@
           mCondition(conditionIndex >= 0 ? false : true),
           mConditionSliced(false),
           mWizard(wizard),
-          mConditionTrackerIndex(conditionIndex){};
+          mConditionTrackerIndex(conditionIndex),
+          mContainANYPositionInDimensionsInWhat(false),
+          mSameConditionDimensionsInTracker(false),
+          mHasLinksToAllConditionDimensionsInTracker(false) {
+    }
 
     virtual ~MetricProducer(){};
 
@@ -219,6 +223,16 @@
     vector<Matcher> mDimensionsInWhat;       // The dimensions_in_what defined in statsd_config
     vector<Matcher> mDimensionsInCondition;  // The dimensions_in_condition defined in statsd_config
 
+    bool mContainANYPositionInDimensionsInWhat;
+
+    // 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;
+
+    // True iff the metric to condition links cover all dimension fields in the condition tracker.
+    // This field is always false for combinational condition trackers.
+    bool mHasLinksToAllConditionDimensionsInTracker;
+
     std::vector<Metric2Condition> mMetric2ConditionLinks;
 
     std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a32e037..dbab814 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -160,10 +160,18 @@
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
-    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
+    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);
+
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index e88daf7..09913dc 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -80,6 +80,7 @@
     mBucketSizeNs = bucketSizeMills * 1000000;
     if (metric.has_dimensions_in_what()) {
         translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+        mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
     if (metric.has_dimensions_in_condition()) {
@@ -146,7 +147,7 @@
 
     for (const auto& pair : mPastBuckets) {
         const MetricDimensionKey& dimensionKey = pair.first;
-        VLOG("  dimension key %s", dimensionKey.c_str());
+        VLOG("  dimension key %s", dimensionKey.toString().c_str());
         uint64_t wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
@@ -254,7 +255,7 @@
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("ValueMetric %lld dropping data for dimension key %s",
-                (long long)mMetricId, newKey.c_str());
+                (long long)mMetricId, newKey.toString().c_str());
             return true;
         }
     }
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 8f236fa..7b3393f 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -64,7 +64,7 @@
                     sp<ConditionWizard> wizard, int conditionIndex,
                     const std::vector<Matcher>& dimensionInCondition, bool nesting,
                     uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs,
-                    uint64_t bucketSizeNs, bool conditionSliced,
+                    uint64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
         : mConfigKey(key),
           mTrackerId(id),
@@ -80,6 +80,7 @@
           mCurrentBucketNum(currentBucketNum),
           mStartTimeNs(startTimeNs),
           mConditionSliced(conditionSliced),
+          mHasLinksToAllConditionDimensionsInTracker(fullLink),
           mAnomalyTrackers(anomalyTrackers){};
 
     virtual ~DurationTracker(){};
@@ -198,6 +199,9 @@
 
     const bool mConditionSliced;
 
+    bool mSameConditionDimensionsInTracker;
+    bool mHasLinksToAllConditionDimensionsInTracker;
+
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
 
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index b225560..8e0bf26 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -30,20 +30,33 @@
                                        const vector<Matcher>& dimensionInCondition, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t currentBucketNum,
                                        uint64_t startTimeNs, uint64_t bucketSizeNs,
-                                       bool conditionSliced,
+                                       bool conditionSliced, bool fullLink,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
                       currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, anomalyTrackers) {
+                      conditionSliced, fullLink, anomalyTrackers) {
+    if (mWizard != nullptr) {
+        mSameConditionDimensionsInTracker =
+            mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
+    }
 }
 
 unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
     auto clonedTracker = make_unique<MaxDurationTracker>(*this);
-    for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) {
-        it->second.lastStartTime = eventTime;
-        it->second.lastDuration = 0;
+    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);
+        }
     }
-    return clonedTracker;
+    if (clonedTracker->mInfos.empty()) {
+        return nullptr;
+    } else {
+        return clonedTracker;
+    }
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -59,7 +72,7 @@
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
-                (long long)mTrackerId, newKey.c_str());
+                (long long)mTrackerId, newKey.toString().c_str());
             return true;
         }
     }
@@ -77,7 +90,7 @@
     if (mConditionSliced) {
         duration.conditionKeys = conditionKey;
     }
-    VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
+    VLOG("MaxDuration: key %s start condition %d", key.toString().c_str(), condition);
 
     switch (duration.state) {
         case kStarted:
@@ -103,7 +116,7 @@
 
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
                                   bool forceStop) {
-    VLOG("MaxDuration: key %s stop", key.c_str());
+    VLOG("MaxDuration: key %s stop", key.toString().c_str());
     if (mInfos.find(key) == mInfos.end()) {
         // we didn't see a start event before. do nothing.
         return;
@@ -120,7 +133,7 @@
                 stopAnomalyAlarm();
                 duration.state = DurationState::kStopped;
                 int64_t durationTime = eventTime - duration.lastStartTime;
-                VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
+                VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(),
                      (long long)duration.lastStartTime, (long long)eventTime,
                      (long long)durationTime);
                 duration.lastDuration += durationTime;
@@ -241,13 +254,15 @@
         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());
-        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+        VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet);
         noteConditionChanged(pair.first, conditionMet, timestamp);
     }
 }
@@ -277,7 +292,7 @@
                     // In case any other dimensions are still started, we need to set the alarm.
                     startAnomalyAlarm(timestamp);
                 }
-                VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
+                VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str());
             }
             break;
         case kStopped:
@@ -290,7 +305,7 @@
                 it->second.state = DurationState::kStarted;
                 it->second.lastStartTime = timestamp;
                 startAnomalyAlarm(timestamp);
-                VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
+                VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str());
             }
             break;
     }
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index c731b75..0452d37 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -33,6 +33,7 @@
                        const std::vector<Matcher>& dimensionInCondition, bool nesting,
                        uint64_t currentBucketStartNs, uint64_t currentBucketNum,
                        uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+                       bool fullLink,
                        const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index f583f91..2358415 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -28,14 +28,18 @@
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
         sp<ConditionWizard> wizard, int conditionIndex, const vector<Matcher>& dimensionInCondition,
         bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
-        uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+        uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, bool fullLink,
         const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
                       currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, anomalyTrackers),
+                      conditionSliced, fullLink, anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
+    if (mWizard != nullptr) {
+        mSameConditionDimensionsInTracker =
+            mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
+    }
 }
 
 unique_ptr<DurationTracker> OringDurationTracker::clone(const uint64_t eventTime) {
@@ -57,7 +61,7 @@
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
             ALOGE("OringDurTracker %lld dropping data for dimension key %s",
-                (long long)mTrackerId, newKey.c_str());
+                (long long)mTrackerId, newKey.toString().c_str());
             return true;
         }
     }
@@ -83,13 +87,13 @@
     if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
         mConditionKeyMap[key] = conditionKey;
     }
-    VLOG("Oring: %s start, condition %d", key.c_str(), condition);
+    VLOG("Oring: %s start, condition %d", key.toString().c_str(), condition);
 }
 
 void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
                                     const bool stopAll) {
     declareAnomalyIfAlarmExpired(timestamp);
-    VLOG("Oring: %s stop", key.c_str());
+    VLOG("Oring: %s stop", key.toString().c_str());
     auto it = mStarted.find(key);
     if (it != mStarted.end()) {
         (it->second)--;
@@ -217,22 +221,26 @@
     if (!mStarted.empty()) {
         for (auto it = mStarted.begin(); it != mStarted.end();) {
             const auto& key = it->first;
-            if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
-                VLOG("Key %s dont have condition key", key.c_str());
+            const auto& condIt = mConditionKeyMap.find(key);
+            if (condIt == mConditionKeyMap.end()) {
+                VLOG("Key %s dont have condition key", key.toString().c_str());
                 ++it;
                 continue;
             }
             std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
             ConditionState conditionState =
-                mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
-                               mDimensionInCondition, &conditionDimensionKeySet);
+                mWizard->query(mConditionTrackerIndex, condIt->second,
+                               mDimensionInCondition,
+                               !mSameConditionDimensionsInTracker,
+                               !mHasLinksToAllConditionDimensionsInTracker,
+                               &conditionDimensionKeySet);
             if (conditionState != ConditionState::kTrue ||
                 (mDimensionInCondition.size() != 0 &&
                  conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) ==
                          conditionDimensionKeySet.end())) {
                 startedToPaused.push_back(*it);
                 it = mStarted.erase(it);
-                VLOG("Key %s started -> paused", key.c_str());
+                VLOG("Key %s started -> paused", key.toString().c_str());
             } else {
                 ++it;
             }
@@ -250,21 +258,24 @@
         for (auto it = mPaused.begin(); it != mPaused.end();) {
             const auto& key = it->first;
             if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
-                VLOG("Key %s dont have condition key", key.c_str());
+                VLOG("Key %s dont have condition key", key.toString().c_str());
                 ++it;
                 continue;
             }
             std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
             ConditionState conditionState =
                 mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
-                               mDimensionInCondition, &conditionDimensionKeySet);
+                               mDimensionInCondition,
+                               !mSameConditionDimensionsInTracker,
+                               !mHasLinksToAllConditionDimensionsInTracker,
+                               &conditionDimensionKeySet);
             if (conditionState == ConditionState::kTrue &&
                 (mDimensionInCondition.size() == 0 ||
                  conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
                          conditionDimensionKeySet.end())) {
                 pausedToStarted.push_back(*it);
                 it = mPaused.erase(it);
-                VLOG("Key %s paused -> started", key.c_str());
+                VLOG("Key %s paused -> started", key.toString().c_str());
             } else {
                 ++it;
             }
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 07c1329..610e3ea 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -32,6 +32,7 @@
                          int conditionIndex, const std::vector<Matcher>& dimensionInCondition,
                          bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
                          uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+                         bool fullLink,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index bbaf50a..80e46d6 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -32,7 +32,7 @@
 // Minimum bucket size in seconds
 const long kMinBucketSizeSec = 5 * 60;
 
-typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
+typedef std::map<int64_t, HashableDimensionKey> ConditionKey;
 
 typedef std::unordered_map<MetricDimensionKey, int64_t> DimToValMap;
 
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 3dc3fd1..e826a52 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -75,10 +75,10 @@
     event->init();
 }
 
-std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
+std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey(
     const Position position,
     const std::vector<int> &uids, const string& conditionName) {
-    std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
+    std::map<int64_t, HashableDimensionKey> outputKeyMap;
     std::vector<int> uid_indexes;
     int pos[] = {1, 1, 1};
     int depth = 2;
@@ -104,7 +104,7 @@
         Value value((int32_t)uids[idx]);
         HashableDimensionKey dim;
         dim.addValue(FieldValue(field, value));
-        outputKeyMap[StringToId(conditionName)].push_back(dim);
+        outputKeyMap[StringToId(conditionName)] = dim;
     }
     return outputKeyMap;
 }
@@ -122,6 +122,7 @@
 
     SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/,
                                             simplePredicate, trackerNameIndexMap);
+    EXPECT_FALSE(conditionTracker.isSliced());
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
 
@@ -193,6 +194,7 @@
 }
 
 TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
+    std::vector<sp<ConditionTracker>> allConditions;
     SimplePredicate simplePredicate;
     simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
     simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
@@ -205,6 +207,7 @@
     SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
+    EXPECT_FALSE(conditionTracker.isSliced());
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
 
@@ -257,14 +260,14 @@
 
     conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
                                        changedCache);
-    // result should still be true
     EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
     EXPECT_TRUE(changedCache[0]);
 }
 
 TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+    std::vector<sp<ConditionTracker>> allConditions;
     for (Position position :
-            { Position::ANY, Position::FIRST, Position::LAST}) {
+            { Position::FIRST, Position::LAST}) {
         vector<Matcher> dimensionInCondition;
         std::unordered_set<HashableDimensionKey> dimensionKeys;
 
@@ -281,6 +284,7 @@
         SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
                                                 0 /*condition tracker index*/, simplePredicate,
                                                 trackerNameIndexMap);
+
         std::vector<int> uids = {111, 222, 333};
 
         LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -305,12 +309,20 @@
             EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
+            EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+        } else {
+            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);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
@@ -331,6 +343,9 @@
         } else {
             EXPECT_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*/);
@@ -350,6 +365,8 @@
         } else {
             EXPECT_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.
@@ -362,18 +379,26 @@
                                            changedCache);
         EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
         EXPECT_TRUE(changedCache[0]);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
+            EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+        } else {
+            EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), uids.size());
+        }
 
         // query again
         conditionCache[0] = ConditionState::kNotEvaluated;
         conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        false, false,
                                         conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
-
     }
 
 }
 
 TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+    std::vector<sp<ConditionTracker>> allConditions;
     vector<Matcher> dimensionInCondition;
     std::unordered_set<HashableDimensionKey> dimensionKeys;
 
@@ -391,6 +416,8 @@
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
 
+    EXPECT_FALSE(conditionTracker.isSliced());
+
     std::vector<int> uid_list1 = {111, 1111, 11111};
     string uid1_wl1 = "wl1_1";
     std::vector<int> uid_list2 = {222, 2222, 22222};
@@ -419,6 +446,7 @@
     conditionCache[0] = ConditionState::kNotEvaluated;
 
     conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                    true, true,
                                     conditionCache, dimensionKeys);
     EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
@@ -463,13 +491,15 @@
     conditionCache[0] = ConditionState::kNotEvaluated;
     dimensionKeys.clear();
     conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                    true, true,
                                     conditionCache, dimensionKeys);
     EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 }
 
 TEST(SimpleConditionTrackerTest, TestStopAll) {
+    std::vector<sp<ConditionTracker>> allConditions;
     for (Position position :
-            {Position::ANY, Position::FIRST, Position::LAST}) {
+            { Position::FIRST, Position::LAST }) {
         vector<Matcher> dimensionInCondition;
         std::unordered_set<HashableDimensionKey> dimensionKeys;
         SimplePredicate simplePredicate = getWakeLockHeldCondition(
@@ -510,12 +540,23 @@
             EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
+        {
+            if (position == Position::FIRST ||
+                position == Position::LAST) {
+                EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+            } else {
+                EXPECT_EQ(uid_list1.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+            }
+        }
 
         // Now test query
         const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
 
         conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        false, false,
                                         conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
@@ -538,11 +579,23 @@
                       conditionTracker.mSlicedConditionState.size());
         }
         EXPECT_TRUE(changedCache[0]);
+        {
+            if (position == Position::FIRST ||
+                position == Position::LAST) {
+                EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+            } else {
+                EXPECT_EQ(uid_list2.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+            }
+        }
+
 
         // TEST QUERY
         const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
         conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        false, false,
                                         conditionCache, dimensionKeys);
 
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
@@ -561,11 +614,22 @@
                                            changedCache);
         EXPECT_TRUE(changedCache[0]);
         EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+        {
+            if (position == Position::FIRST || position == Position::LAST) {
+                EXPECT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+            } else {
+                EXPECT_EQ(uid_list1.size() + uid_list2.size(),
+                          conditionTracker.getChangedToFalseDimensions(allConditions)->size());
+                EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+            }
+        }
 
         // TEST QUERY
         const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
         conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        false, false,
                                         conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
@@ -573,6 +637,7 @@
         const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
         conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        false, false,
                                         conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
     }
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
new file mode 100644
index 0000000..a08f606
--- /dev/null
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
@@ -0,0 +1,879 @@
+// 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) {
+    StatsdConfig config;
+    *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.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
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) {
+    for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) {
+        for (auto aggregationType : {DurationMetric::MAX_SPARSE, DurationMetric::SUM}) {
+            ConfigKey cfgKey;
+            auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+                    aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension);
+            int64_t bucketStartTimeNs = 10000000000;
+            int64_t bucketSizeNs =
+                    TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+            auto processor = CreateStatsLogProcessor(
+                    bucketStartTimeNs / NS_PER_SEC, 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, &buffer);
+            EXPECT_TRUE(buffer.size() > 0);
+            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+            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 - 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);
+                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 - 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(), 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 - 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);
+                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 - 600 + 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_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 / NS_PER_SEC, 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, &buffer);
+            EXPECT_TRUE(buffer.size() > 0);
+            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+            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) {
+    StatsdConfig config;
+    *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.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 (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+        ConfigKey cfgKey;
+        auto config =
+                CreateDurationMetricConfig_PartialLink_AND_CombinationCondition(aggregationType);
+        int64_t bucketStartTimeNs = 10000000000;
+        int64_t bucketSizeNs =
+                TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+        auto processor = CreateStatsLogProcessor(
+                bucketStartTimeNs / NS_PER_SEC, 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, &buffer);
+        EXPECT_TRUE(buffer.size() > 0);
+        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+        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_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
similarity index 85%
rename from cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
rename to cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
index 01348bd..435e199 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
@@ -28,7 +28,7 @@
 
 namespace {
 
-StatsdConfig CreateCountMetricWithNoLinkConfig() {
+StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() {
     StatsdConfig config;
     auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher();
     *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher;
@@ -67,9 +67,9 @@
 
 }  // namespace
 
-TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) {
+TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition) {
     ConfigKey cfgKey;
-    auto config = CreateCountMetricWithNoLinkConfig();
+    auto config = CreateCountMetric_NoLink_CombinationCondition_Config();
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
@@ -227,7 +227,7 @@
 
 namespace {
 
-StatsdConfig CreateCountMetricWithLinkConfig() {
+StatsdConfig CreateCountMetric_Link_CombinationCondition() {
     StatsdConfig config;
     auto appCrashMatcher = CreateProcessCrashAtomMatcher();
     *config.add_atom_matcher() = appCrashMatcher;
@@ -274,9 +274,9 @@
 
 }  // namespace
 
-TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) {
+TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition) {
     ConfigKey cfgKey;
-    auto config = CreateCountMetricWithLinkConfig();
+    auto config = CreateCountMetric_Link_CombinationCondition();
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs =
             TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
@@ -413,7 +413,8 @@
 
 namespace {
 
-StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) {
+StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition(
+        DurationMetric::AggregationType aggregationType) {
     StatsdConfig config;
     *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
@@ -445,6 +446,7 @@
     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;
@@ -452,10 +454,10 @@
 
 }  // namespace
 
-TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) {
-    for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) {
+    for (auto aggregationType : { DurationMetric::MAX_SPARSE}) { // DurationMetric::SUM,
         ConfigKey cfgKey;
-        auto config = CreateDurationMetricConfigNoLink(aggregationType);
+        auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType);
         int64_t bucketStartTimeNs = 10000000000;
         int64_t bucketSizeNs =
                 TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
@@ -529,43 +531,77 @@
         auto data = metrics.data(0);
         EXPECT_FALSE(data.dimensions_in_what().has_field());
         EXPECT_FALSE(data.dimensions_in_condition().has_field());
-        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);
+        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);
-        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
+
+        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).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);
+        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);
-        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
+        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).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);
+        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 CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) {
+StatsdConfig CreateDurationMetricConfig_Link_CombinationCondition(
+        DurationMetric::AggregationType aggregationType) {
     StatsdConfig config;
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
@@ -599,6 +635,7 @@
     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(
@@ -617,10 +654,10 @@
 
 }  // namespace
 
-TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) {
+TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition) {
     for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
         ConfigKey cfgKey;
-        auto config = CreateDurationMetricConfigWithLink(aggregationType);
+        auto config = CreateDurationMetricConfig_Link_CombinationCondition(aggregationType);
         int64_t bucketStartTimeNs = 10000000000;
         int64_t bucketSizeNs =
                 TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
@@ -701,26 +738,50 @@
         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(), 2);
-        EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
-        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);
+        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");
-        EXPECT_EQ(data.bucket_info_size(), 2);
-        EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 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(), 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);
+        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);
+        }
     }
 }
 
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
new file mode 100644
index 0000000..75ceafb
--- /dev/null
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
@@ -0,0 +1,799 @@
+// 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_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 / NS_PER_SEC, 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, &buffer);
+            EXPECT_TRUE(buffer.size() > 0);
+            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+            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_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 / NS_PER_SEC, 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, &buffer);
+            EXPECT_TRUE(buffer.size() > 0);
+            EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+            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_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 / NS_PER_SEC, 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, &buffer);
+        EXPECT_TRUE(buffer.size() > 0);
+        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+        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/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 9a0de0d..a07683e 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -175,9 +175,9 @@
         {getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
+    EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
 
-    EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
+    EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
 
     CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
                                       bucketStartTimeNs);
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index 8246268..23d3171 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -113,9 +113,9 @@
     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(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
 
-    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);
 
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 83b1cbf..57a8925 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -42,14 +42,13 @@
 const int TagId = 1;
 
 const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
-const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1");
 const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
 const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
@@ -66,7 +65,7 @@
     int64_t metricId = 1;
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, {});
+                               false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
@@ -86,7 +85,6 @@
 
 TEST(MaxDurationTrackerTest, TestStopAll) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
@@ -103,7 +101,7 @@
     int64_t metricId = 1;
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, {});
+                               false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
@@ -124,7 +122,6 @@
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
     vector<Matcher> dimensionInCondition;
@@ -140,7 +137,7 @@
     int64_t metricId = 1;
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, {});
+                               false, false, {});
 
     // The event starts.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -166,7 +163,6 @@
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
     vector<Matcher> dimensionInCondition;
@@ -182,7 +178,7 @@
     int64_t metricId = 1;
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
                                true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, {});
+                               false, false, {});
 
     // 2 starts
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -204,14 +200,14 @@
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
-    const std::vector<HashableDimensionKey> conditionKey = {key1};
+    const HashableDimensionKey conditionDimKey = key1;
 
     vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 1, "1");
-    conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
+    conditionKey1[StringToId("APP_BACKGROUND")] = conditionDimKey;
 
     /**
     Start in first bucket, stop in second bucket. Condition turns on and off in the first bucket
@@ -229,7 +225,7 @@
     int64_t metricId = 1;
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
-                               {});
+                               false, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -250,8 +246,6 @@
 }
 
 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
     vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -281,7 +275,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, {anomalyTracker});
+                               true, false, {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -301,8 +295,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) {
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
     vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -310,7 +302,7 @@
     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
     ConditionKey conditionKey2;
-    conditionKey2[StringToId("APP_BACKGROUND")] = {getMockedDimensionKey(TagId, 4, "2")};
+    conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
@@ -343,7 +335,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, {anomalyTracker});
+                               true, false, {anomalyTracker});
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -360,8 +352,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) {
-    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
     vector<Matcher> dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -369,7 +359,7 @@
     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
     ConditionKey conditionKey2;
-    conditionKey2[StringToId("APP_BACKGROUND")] = {getMockedDimensionKey(TagId, 4, "2")};
+    conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
 
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
@@ -399,7 +389,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, {anomalyTracker});
+                               true, false, {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
@@ -415,4 +405,4 @@
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index aa41038..54abcb2 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -40,7 +40,7 @@
 const int64_t metricId = 123;
 const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
 
-const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps");
 const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
 const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
 const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -48,8 +48,6 @@
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -65,7 +63,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+                                 bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -83,8 +81,6 @@
 TEST(OringDurationTrackerTest, TestDurationNested) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -99,7 +95,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+                                 bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -132,7 +128,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+                                 bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -148,8 +144,6 @@
 TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -165,7 +159,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+                                 bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -189,8 +183,6 @@
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -199,7 +191,7 @@
     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;
@@ -212,7 +204,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, {});
+                                 bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -229,8 +221,6 @@
 TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -239,7 +229,7 @@
     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));
@@ -254,7 +244,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, {});
+                                 bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -273,8 +263,6 @@
 TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -283,7 +271,7 @@
     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;
@@ -295,7 +283,7 @@
 
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, {});
+                                 bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -315,8 +303,6 @@
 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -339,7 +325,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, {anomalyTracker});
+                                 bucketSizeNs, true, false, {anomalyTracker});
 
     // Nothing in the past bucket.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -386,7 +372,6 @@
 TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -410,7 +395,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, {anomalyTracker});
+                                 bucketSizeNs, false, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
@@ -437,8 +422,6 @@
 TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
 
-    const std::vector<HashableDimensionKey> kConditionKey1 =
-        {getMockedDimensionKey(TagId, 1, "maps")};
     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
     vector<Matcher> dimensionInCondition;
@@ -462,7 +445,7 @@
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
                                  true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
-                                 bucketSizeNs, false, {anomalyTracker});
+                                 bucketSizeNs, false, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
@@ -508,4 +491,4 @@
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index a01de63..f040bf9 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -26,9 +26,10 @@
 
 class MockConditionWizard : public ConditionWizard {
 public:
-    MOCK_METHOD4(query,
+    MOCK_METHOD6(query,
                  ConditionState(const int conditionIndex, const ConditionKey& conditionParameters,
                                 const vector<Matcher>& dimensionFields,
+                                const bool isSubsetDim, const bool isPartialLink,
                                 std::unordered_set<HashableDimensionKey>* dimensionKeySet));
 };
 
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 242b6eb..2678c8a 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -26,6 +26,28 @@
     return atom_matcher;
 }
 
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
+                                                      ScheduledJobStateChanged::State 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::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);
+    return atom_matcher;
+}
+
+AtomMatcher CreateStartScheduledJobAtomMatcher() {
+    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart",
+                                                     ScheduledJobStateChanged::STARTED);
+}
+
+AtomMatcher CreateFinishScheduledJobAtomMatcher() {
+    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish",
+                                                     ScheduledJobStateChanged::FINISHED);
+}
+
 AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
@@ -168,6 +190,14 @@
         "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
 }
 
+Predicate CreateScheduledJobPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
+    return predicate;
+}
+
 Predicate CreateBatterySaverModePredicate() {
     Predicate predicate;
     predicate.set_id(StringToId("BatterySaverIsOn"));
@@ -290,6 +320,32 @@
 
 }
 
+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;
+}
+
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+    const std::vector<AttributionNodeInternal>& attributions,
+    const string& name, uint64_t timestampNs) {
+    return CreateScheduledJobStateChangedEvent(
+            attributions, name, 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> CreateWakelockStateChangedEvent(
         const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
         const WakelockStateChanged::State state, uint64_t timestampNs) {
@@ -419,7 +475,6 @@
 
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
     // Uid only.
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1708cc3..14eba1f 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -28,6 +28,15 @@
 // Create AtomMatcher proto to simply match a specific atom type.
 AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
 
+// Create AtomMatcher proto for scheduled job state changed.
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
+
+// Create AtomMatcher proto for starting a scheduled job.
+AtomMatcher CreateStartScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for a scheduled job is done.
+AtomMatcher CreateFinishScheduledJobAtomMatcher();
+
 // Create AtomMatcher proto for screen brightness state changed.
 AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
 
@@ -73,6 +82,9 @@
 // Create Predicate proto for screen is off.
 Predicate CreateScreenIsOffPredicate();
 
+// Create Predicate proto for a running scheduled job.
+Predicate CreateScheduledJobPredicate();
+
 // Create Predicate proto for battery saver mode.
 Predicate CreateBatterySaverModePredicate();
 
@@ -107,6 +119,16 @@
 std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
    int level, uint64_t timestampNs);
 
+// Create log event when scheduled job starts.
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+    const std::vector<AttributionNodeInternal>& attributions,
+    const string& name, uint64_t timestampNs);
+
+// 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 when battery saver starts.
 std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs);
 // Create log event when battery saver stops.