Merge "Consider only the top most stacks for visibility test"
diff --git a/api/current.txt b/api/current.txt
index c63ddbf..cebd6d6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -39326,7 +39326,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39458,6 +39460,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -39648,6 +39651,7 @@
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
diff --git a/api/system-current.txt b/api/system-current.txt
index 119040e..e1f2272 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -42652,7 +42652,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -42791,6 +42793,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -42984,6 +42987,7 @@
     method public deprecated void onPhoneCreated(android.telecom.Phone);
     method public deprecated void onPhoneDestroyed(android.telecom.Phone);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
@@ -43122,6 +43126,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Call> getCalls();
     method public final void removeListener(android.telecom.Phone.Listener);
+    method public void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 8062d16..92b41a8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -39717,7 +39717,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39852,6 +39854,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void sendRemoteRttRequest();
     method public final void sendRttInitiationFailure(int);
@@ -40055,6 +40058,7 @@
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index da88ee3..3cef6b3 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -65,7 +65,7 @@
 TEST(IhUtilTest, Reader) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path));
 
     Reader r(tf.fd);
     string line;
@@ -82,7 +82,7 @@
 TEST(IhUtilTest, ReaderSmallBufSize) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path));
 
     Reader r(tf.fd, 5);
     string line;
@@ -99,7 +99,7 @@
 TEST(IhUtilTest, ReaderEmpty) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("", tf.path));
 
     Reader r(tf.fd);
     string line;
@@ -112,7 +112,7 @@
 TEST(IhUtilTest, ReaderMultipleEmptyLines) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path));
 
     Reader r(tf.fd);
     string line;
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 2afa778..3fd2ed8 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -84,7 +84,7 @@
 
 TEST_F(FdBufferTest, ReadAndWrite) {
     std::string testdata = "FdBuffer test string";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
     ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
     AssertBufferReadSuccessful(testdata.size());
     AssertBufferContent(testdata.c_str());
@@ -97,7 +97,7 @@
 
 TEST_F(FdBufferTest, ReadAndIterate) {
     std::string testdata = "FdBuffer test string";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
     ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
 
     int i=0;
@@ -137,7 +137,7 @@
 TEST_F(FdBufferTest, ReadInStreamAndWrite) {
     std::string testdata = "simply test read in stream";
     std::string expected = HEAD + testdata;
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -166,7 +166,7 @@
 TEST_F(FdBufferTest, ReadInStreamAndWriteAllAtOnce) {
     std::string testdata = "child process flushes only after all data are read.";
     std::string expected = HEAD + testdata;
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -196,7 +196,7 @@
 }
 
 TEST_F(FdBufferTest, ReadInStreamEmpty) {
-    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("", tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -260,7 +260,7 @@
 
 TEST_F(FdBufferTest, ReadInStreamTimeOut) {
     std::string testdata = "timeout test";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 84a2a82..ca94623 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -60,7 +60,7 @@
     }
 
     void writeToFdBuffer(string str) {
-        ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+        ASSERT_TRUE(WriteStringToFile(str, tf.path));
         ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
         ASSERT_EQ(str.size(), buffer.size());
     }
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 25b05b2..649e908 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -99,7 +99,7 @@
     ReportRequestSet requests;
 
     ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
 
     requests.setMainFd(STDOUT_FILENO);
 
@@ -173,7 +173,7 @@
     ReportRequestSet requests;
 
     ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
 
     requests.setMainFd(STDOUT_FILENO);
 
@@ -186,7 +186,7 @@
     TemporaryFile input;
     FileSection fs(NOOP_PARSER, input.path);
     ReportRequestSet requests;
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args;
     args.setAll(true);
@@ -205,7 +205,7 @@
     TemporaryFile input;
     FileSection fs(NOOP_PARSER, input.path);
     ReportRequestSet requests;
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args;
     args.setAll(true);
@@ -223,7 +223,7 @@
     ASSERT_TRUE(output1.fd != -1);
     ASSERT_TRUE(output2.fd != -1);
     ASSERT_TRUE(output3.fd != -1);
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args1, args2, args3;
     args1.setAll(true);
@@ -265,7 +265,7 @@
     ASSERT_TRUE(output2.fd != -1);
     ASSERT_TRUE(output3.fd != -1);
 
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args1, args2, args3, args4;
     args1.setAll(true);
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index d99136f..929c3cc 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -151,19 +151,19 @@
     $(statsd_common_src) \
     tests/AnomalyMonitor_test.cpp \
     tests/anomaly/AnomalyTracker_test.cpp \
-    tests/ConditionTracker_test.cpp \
     tests/ConfigManager_test.cpp \
     tests/indexed_priority_queue_test.cpp \
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
     tests/UidMap_test.cpp \
+    tests/condition/CombinationConditionTracker_test.cpp \
+    tests/condition/SimpleConditionTracker_test.cpp \
     tests/metrics/OringDurationTracker_test.cpp \
     tests/metrics/MaxDurationTracker_test.cpp \
     tests/metrics/CountMetricProducer_test.cpp \
     tests/metrics/EventMetricProducer_test.cpp
 
-
 LOCAL_STATIC_LIBRARIES := \
     libgmock
 
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 953bcb3..41f5fca 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -115,49 +115,47 @@
             evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
 }
 
-bool CombinationConditionTracker::evaluateCondition(
+void CombinationConditionTracker::evaluateCondition(
         const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
         const std::vector<sp<ConditionTracker>>& mAllConditions,
         std::vector<ConditionState>& nonSlicedConditionCache,
-        std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
+        std::vector<bool>& conditionChangedCache) {
     // value is up to date.
     if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        return false;
+        return;
     }
 
     for (const int childIndex : mChildren) {
         if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
             const sp<ConditionTracker>& child = mAllConditions[childIndex];
             child->evaluateCondition(event, eventMatcherValues, mAllConditions,
-                                     nonSlicedConditionCache, nonSlicedChangedCache,
-                                     slicedConditionChanged);
+                                     nonSlicedConditionCache, conditionChangedCache);
         }
     }
 
-    ConditionState newCondition =
-            evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
+    if (!mSliced) {
+        ConditionState newCondition =
+                evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
 
-    bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
-    mNonSlicedConditionState = newCondition;
+        bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+        mNonSlicedConditionState = newCondition;
 
-    nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
+        nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
 
-    nonSlicedChangedCache[mIndex] = nonSlicedChanged;
-
-    if (mSliced) {
+        conditionChangedCache[mIndex] = nonSlicedChanged;
+    } else {
         for (const int childIndex : mChildren) {
             // If any of the sliced condition in children condition changes, the combination
             // condition may be changed too.
-            if (slicedConditionChanged[childIndex]) {
-                slicedConditionChanged[mIndex] = true;
+            if (conditionChangedCache[childIndex]) {
+                conditionChangedCache[mIndex] = true;
                 break;
             }
         }
+        nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
         ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
-              slicedConditionChanged[mIndex] == true);
+              conditionChangedCache[mIndex] == true);
     }
-
-    return nonSlicedChanged;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index dbdb3b7..3d2c6bb 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -35,12 +35,11 @@
               const std::unordered_map<std::string, int>& conditionNameIndexMap,
               std::vector<bool>& stack) override;
 
-    bool evaluateCondition(const LogEvent& event,
+    void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache,
-                           std::vector<bool>& slicedConditionMayChanged) override;
+                           std::vector<bool>& changedCache) override;
 
     void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index bb5ddeb..0ac7ef3 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -56,25 +56,20 @@
                       std::vector<bool>& stack) = 0;
 
     // evaluate current condition given the new event.
-    // return true if the condition state changed, false if the condition state is not changed.
     // event: the new log event
     // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
     //                     event before ConditionTrackers, because ConditionTracker depends on
     //                     LogMatchingTrackers.
     // mAllConditions: the list of all ConditionTracker
     // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
-    // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
-    // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
-    //      Because sliced condition needs parameters to determine the value. So the sliced
-    //      condition is not pushed to metrics. We only inform the relevant metrics that the sliced
-    //      condition may have changed, and metrics should pull the conditions that they are
-    //      interested in.
-    virtual bool evaluateCondition(const LogEvent& event,
+    // conditionChanged: the bit map to record whether the condition has changed.
+    //                   If the condition has dimension, then any sub condition changes will report
+    //                   conditionChanged.
+    virtual void evaluateCondition(const LogEvent& event,
                                    const std::vector<MatchingState>& eventMatcherValues,
                                    const std::vector<sp<ConditionTracker>>& mAllConditions,
                                    std::vector<ConditionState>& conditionCache,
-                                   std::vector<bool>& nonSlicedConditionChanged,
-                                   std::vector<bool>& slicedConditionMayChanged) = 0;
+                                   std::vector<bool>& conditionChanged) = 0;
 
     // Return the current condition state.
     virtual ConditionState isConditionMet() {
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index b691faea..a694dbf 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -74,13 +74,21 @@
         mStopAllLogMatcherIndex = -1;
     }
 
-    mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
-                      simpleCondition.dimension().end());
+    mOutputDimension.insert(mOutputDimension.begin(), simpleCondition.dimension().begin(),
+                            simpleCondition.dimension().end());
 
-    if (mDimension.size() > 0) {
+    if (mOutputDimension.size() > 0) {
         mSliced = true;
     }
 
+    if (simpleCondition.initial_value() == SimpleCondition_InitialValue_FALSE) {
+        mInitialValue = ConditionState::kFalse;
+    } else {
+        mInitialValue = ConditionState::kUnknown;
+    }
+
+    mNonSlicedConditionState = mInitialValue;
+
     mInitialized = true;
 }
 
@@ -97,127 +105,166 @@
     return mInitialized;
 }
 
-void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+void print(map<HashableDimensionKey, int>& conditions, const string& name) {
     VLOG("%s DUMP:", name.c_str());
-
     for (const auto& pair : conditions) {
-        VLOG("\t%s %d", pair.first.c_str(), pair.second);
+        VLOG("\t%s : %d", pair.first.c_str(), pair.second);
     }
 }
 
-bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
+void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
+                                           std::vector<bool>& conditionChangedCache) {
+    // Unless the default condition is false, and there was nothing started, otherwise we have
+    // triggered a condition change.
+    conditionChangedCache[mIndex] =
+            (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false
+                                                                                           : true;
+
+    // After StopAll, we know everything has stopped. From now on, default condition is false.
+    mInitialValue = ConditionState::kFalse;
+    mSlicedConditionState.clear();
+    conditionCache[mIndex] = ConditionState::kFalse;
+}
+
+void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
+                                                  bool matchStart,
+                                                  std::vector<ConditionState>& conditionCache,
+                                                  std::vector<bool>& conditionChangedCache) {
+    bool changed = false;
+    auto outputIt = mSlicedConditionState.find(outputKey);
+    ConditionState newCondition;
+    if (outputIt == mSlicedConditionState.end()) {
+        // We get a new output key.
+        newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
+        if (matchStart && mInitialValue != ConditionState::kTrue) {
+            mSlicedConditionState[outputKey] = 1;
+            changed = true;
+        } 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[outputKey] = 0;
+            changed = true;
+        }
+    } else {
+        // we have history about this output key.
+        auto& startedCount = outputIt->second;
+        // assign the old value first.
+        newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+        if (matchStart) {
+            if (startedCount == 0) {
+                // This condition for this output key will change from false -> true
+                changed = true;
+            }
+
+            // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated
+            // as 1 if not counting nesting.
+            startedCount++;
+            newCondition = ConditionState::kTrue;
+        } else {
+            // This is a stop event.
+            if (startedCount > 0) {
+                if (mCountNesting) {
+                    startedCount--;
+                    if (startedCount == 0) {
+                        newCondition = ConditionState::kFalse;
+                    }
+                } else {
+                    // not counting nesting, so ignore the number of starts, stop now.
+                    startedCount = 0;
+                    newCondition = ConditionState::kFalse;
+                }
+                // if everything has stopped for this output key, condition true -> false;
+                if (startedCount == 0) {
+                    changed = true;
+                }
+            }
+
+            // 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());
+            }
+        }
+    }
+
+    // dump all dimensions for debugging
+    if (DEBUG) {
+        print(mSlicedConditionState, mName);
+    }
+
+    conditionChangedCache[mIndex] = changed;
+    conditionCache[mIndex] = newCondition;
+
+    VLOG("SimpleCondition %s nonSlicedChange? %d", mName.c_str(),
+         conditionChangedCache[mIndex] == true);
+}
+
+void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                                const vector<MatchingState>& eventMatcherValues,
                                                const vector<sp<ConditionTracker>>& mAllConditions,
                                                vector<ConditionState>& conditionCache,
-                                               vector<bool>& nonSlicedConditionChanged,
-                                               std::vector<bool>& slicedConditionChanged) {
+                                               vector<bool>& conditionChangedCache) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
         VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
-        return false;
+        return;
     }
 
-    // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
+    if (mStopAllLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+        handleStopAll(conditionCache, conditionChangedCache);
+        return;
+    }
 
-    ConditionState newCondition = mNonSlicedConditionState;
-    bool matched = false;
+    int matchedState = -1;
     // Note: The order to evaluate the following start, stop, stop_all matters.
     // The priority of overwrite is stop_all > stop > start.
     if (mStartLogMatcherIndex >= 0 &&
         eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kTrue;
+        matchedState = 1;
     }
 
     if (mStopLogMatcherIndex >= 0 &&
         eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kFalse;
+        matchedState = 0;
     }
 
-    bool stopAll = false;
-    if (mStopAllLogMatcherIndex >= 0 &&
-        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kFalse;
-        stopAll = true;
-    }
-
-    if (matched == false) {
-        slicedConditionChanged[mIndex] = false;
-        nonSlicedConditionChanged[mIndex] = false;
+    if (matchedState < 0) {
+        conditionChangedCache[mIndex] = false;
         conditionCache[mIndex] = mNonSlicedConditionState;
-        return false;
+        return;
     }
 
-    bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
-
-    bool slicedChanged = false;
-
-    if (stopAll) {
-        // TODO: handle stop all; all dimension should be cleared.
-    }
-
-
-    if (mDimension.size() > 0) {
-        HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
-        if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
-            mSlicedConditionState[hashableKey] != newCondition) {
-            slicedChanged = true;
-            mSlicedConditionState[hashableKey] = newCondition;
-        }
-        VLOG("key: %s %d", hashableKey.c_str(), newCondition);
-        // dump all dimensions for debugging
-        if (DEBUG) {
-            print(mSlicedConditionState, mName);
-        }
-    }
-
-    // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
-    // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
-    // so mark the slicedConditionChanged to be true.
-    // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
-    //     APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
-    //     SCREEN_OFF is not sliced, and it changes from False -> True;
-    //     We need to populate this change to parent condition. Because for App_B,
-    //     the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
-    slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
-    nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
-
-    VLOG("SimpleCondition %s nonSlicedChange? %d  SlicedChanged? %d", mName.c_str(),
-         nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
-    mNonSlicedConditionState = newCondition;
-    conditionCache[mIndex] = mNonSlicedConditionState;
-
-    return nonSlicedConditionChanged[mIndex];
+    // outputKey is the output key values. e.g, uid:1234
+    const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
+    handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
 }
 
 void SimpleConditionTracker::isConditionMet(
         const map<string, HashableDimensionKey>& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
     const auto pair = conditionParameters.find(mName);
-    if (pair == conditionParameters.end()) {
-        // the query does not need my sliced condition. just return the non sliced condition.
-        conditionCache[mIndex] = mNonSlicedConditionState;
-        VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+    HashableDimensionKey key =
+            (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;
+
+    if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
+        ALOGE("Condition %s output has dimension, but it's not specified in the query!",
+              mName.c_str());
+        conditionCache[mIndex] = mInitialValue;
         return;
     }
 
-    const HashableDimensionKey& key = pair->second;
     VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
 
-    if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
-        // never seen this key before. the condition is unknown to us.
-        conditionCache[mIndex] = ConditionState::kUnknown;
+    auto startedCountIt = mSlicedConditionState.find(key);
+    if (startedCountIt == mSlicedConditionState.end()) {
+        conditionCache[mIndex] = mInitialValue;
     } else {
-        conditionCache[mIndex] = mSlicedConditionState[key];
+        conditionCache[mIndex] =
+                startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
     }
 
     VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
-
-    if (DEBUG) {
-        print(mSlicedConditionState, mName);
-    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index b72157b..2eda0b1 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -17,6 +17,7 @@
 #ifndef SIMPLE_CONDITION_TRACKER_H
 #define SIMPLE_CONDITION_TRACKER_H
 
+#include <gtest/gtest_prod.h>
 #include "ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
@@ -38,12 +39,11 @@
               const std::unordered_map<std::string, int>& conditionNameIndexMap,
               std::vector<bool>& stack) override;
 
-    bool evaluateCondition(const LogEvent& event,
+    void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache,
-                           std::vector<bool>& slicedChangedCache) override;
+                           std::vector<bool>& changedCache) override;
 
     void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
@@ -62,15 +62,22 @@
     // The index of the LogEventMatcher which defines the stop all.
     int mStopAllLogMatcherIndex;
 
-    // The dimension defines at the atom level, how start and stop should match.
-    // e.g., APP_IN_FOREGROUND, the dimension should be the uid field. Each "start" and
-    // "stop" tells you the state change of a particular app. Without this dimension, this
-    // condition does not make sense.
-    std::vector<KeyMatcher> mDimension;
+    ConditionState mInitialValue;
 
-    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
-    // that StatsLogReport wants.
-    std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
+    std::vector<KeyMatcher> mOutputDimension;
+
+    std::map<HashableDimensionKey, int> mSlicedConditionState;
+
+    void handleStopAll(std::vector<ConditionState>& conditionCache,
+                       std::vector<bool>& changedCache);
+
+    void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
+                              std::vector<ConditionState>& conditionCache,
+                              std::vector<bool>& changedCache);
+
+    FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
+    FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
+    FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 94566ff..d86ab57 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -200,7 +200,7 @@
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -214,7 +214,7 @@
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -226,7 +226,7 @@
     durationMetric->set_metric_id(7);
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -334,12 +334,14 @@
     SimpleCondition* simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_TURNED_ON");
     simpleCondition->set_stop("SCREEN_TURNED_OFF");
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("SCREEN_IS_OFF");
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_TURNED_OFF");
     simpleCondition->set_stop("SCREEN_TURNED_ON");
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND");
@@ -348,6 +350,7 @@
     simpleCondition->set_stop("APP_GOES_FOREGROUND");
     KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
     condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -357,7 +360,7 @@
     combination_condition->add_condition("SCREEN_IS_ON");
 
     condition = config.add_condition();
-    condition->set_name("WL_STATE_PER_APP_PER_NAME");
+    condition->set_name("WL_HELD_PER_APP_PER_NAME");
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("APP_GET_WL");
     simpleCondition->set_stop("APP_RELEASE_WL");
@@ -365,6 +368,17 @@
     condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
     condition_dimension = simpleCondition->add_dimension();
     condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+    simpleCondition->set_count_nesting(true);
+
+    condition = config.add_condition();
+    condition->set_name("WL_HELD_PER_APP");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GET_WL");
+    simpleCondition->set_stop("APP_RELEASE_WL");
+    simpleCondition->set_initial_value(SimpleCondition_InitialValue_FALSE);
+    condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+    simpleCondition->set_count_nesting(true);
 
     return config;
 }
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 19317ee..2d9f369 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -104,18 +104,17 @@
                                           ConditionState::kNotEvaluated);
     // A bitmap to track if a condition has changed value.
     vector<bool> changedCache(mAllConditionTrackers.size(), false);
-    vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
         if (conditionToBeEvaluated[i] == false) {
             continue;
         }
         sp<ConditionTracker>& condition = mAllConditionTrackers[i];
         condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
-                                     changedCache, slicedChangedCache);
+                                     changedCache);
     }
 
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
-        if (changedCache[i] == false && slicedChangedCache[i] == false) {
+        if (changedCache[i] == false) {
             continue;
         }
         auto pair = mConditionToMetricMap.find(i);
@@ -124,14 +123,13 @@
             for (auto metricIndex : metricList) {
                 // metric cares about non sliced condition, and it's changed.
                 // Push the new condition to it directly.
-                if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
+                if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
                     mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
                                                                          eventTime);
                     // metric cares about sliced conditions, and it may have changed. Send
                     // notification, and the metric can query the sliced conditions that are
                     // interesting to it.
-                } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
-                           slicedChangedCache[i]) {
+                } else if (mAllMetricProducers[metricIndex]->isConditionSliced()) {
                     mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
                 }
             }
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 3b8eeaf..a07f76a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -83,7 +83,13 @@
 
   optional string stop_all = 4;
 
-  repeated KeyMatcher dimension = 5;
+  enum InitialValue {
+    UNKNOWN = 0;
+    FALSE = 1;
+  }
+  optional InitialValue initial_value = 5 [default = UNKNOWN];
+
+  repeated KeyMatcher dimension = 6;
 }
 
 message Condition {
diff --git a/cmds/statsd/tests/ConditionTracker_test.cpp b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
similarity index 98%
rename from cmds/statsd/tests/ConditionTracker_test.cpp
rename to cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
index 2935ac71..23d6926 100644
--- a/cmds/statsd/tests/ConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
@@ -23,7 +23,6 @@
 using namespace android::os::statsd;
 using std::vector;
 
-
 #ifdef __ANDROID__
 TEST(ConditionTrackerTest, TestUnknownCondition) {
     LogicalOperation operation = LogicalOperation::AND;
@@ -39,7 +38,7 @@
     conditionResults.push_back(ConditionState::kTrue);
 
     EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults),
-            ConditionState::kUnknown);
+              ConditionState::kUnknown);
 }
 TEST(ConditionTrackerTest, TestAndCondition) {
     // Set up the matcher
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
new file mode 100644
index 0000000..05aad29
--- /dev/null
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -0,0 +1,469 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "src/condition/SimpleConditionTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using std::map;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SimpleCondition getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
+                                         bool outputSlicedUid) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("WAKE_LOCK_ACQUIRE");
+    simpleCondition.set_stop("WAKE_LOCK_RELEASE");
+    simpleCondition.set_stop_all("RELEASE_ALL");
+    if (outputSlicedUid) {
+        KeyMatcher* keyMatcher = simpleCondition.add_dimension();
+        keyMatcher->set_key(1);
+    }
+
+    simpleCondition.set_count_nesting(countNesting);
+    simpleCondition.set_initial_value(defaultFalse ? SimpleCondition_InitialValue_FALSE
+                                                       : SimpleCondition_InitialValue_UNKNOWN);
+    return simpleCondition;
+}
+
+void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
+    auto list = event->GetAndroidLogEventList();
+    *list << uid;  // uid
+    *list << wl;
+    *list << acquire;
+    event->init();
+}
+
+map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
+                                                      const string& conditionName) {
+    // test query
+    KeyValuePair kv1;
+    kv1.set_key(key);
+    kv1.set_value_int(uid);
+    vector<KeyValuePair> kv_list;
+    kv_list.push_back(kv1);
+    map<string, HashableDimensionKey> queryKey;
+    queryKey[conditionName] = getHashableKey(kv_list);
+    return queryKey;
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("SCREEN_TURNED_ON");
+    simpleCondition.set_stop("SCREEN_TURNED_OFF");
+    simpleCondition.set_count_nesting(false);
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // not matched start or stop. condition doesn't change
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // prepare a case for match start.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // now condition should change to true.
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // the case for match stop.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    // condition changes to false.
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // match stop again.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // condition should still be false. not changed.
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("SCREEN_TURNED_ON");
+    simpleCondition.set_stop("SCREEN_TURNED_OFF");
+    simpleCondition.set_count_nesting(true);
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // prepare for another matched start.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // ONE MATCHED STOP
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // result should still be true
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // ANOTHER MATCHED STOP
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // result should still be true
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+    string conditionName = "WL_HELD_BY_UID2";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid = 111;
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid, "wl1", 1);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    const auto queryKey = getWakeLockQueryKey(1, uid, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by this uid
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid, "wl2", 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_FALSE(changedCache[0]);
+
+    // wake lock 1 release
+    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event3, uid, "wl1", 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // nothing changes, because wake lock 2 is still held for this uid
+    EXPECT_FALSE(changedCache[0]);
+
+    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event4, uid, "wl2", 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // query again
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, false /*slice output by uid*/);
+    string conditionName = "WL_HELD";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid1 = 111;
+    string uid1_wl1 = "wl1_1";
+    int uid2 = 222;
+    string uid2_wl1 = "wl2_1";
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid1, uid1_wl1, 1);
+
+    // one matched start for uid1
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    map<string, HashableDimensionKey> queryKey;
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by this uid
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid2, uid2_wl1, 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_FALSE(changedCache[0]);
+
+    // uid1 wake lock 1 release
+    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event3, uid1, uid1_wl1, 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // nothing changes, because uid2 is still holding wl.
+    EXPECT_FALSE(changedCache[0]);
+
+    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event4, uid2, uid2_wl1, 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // query again
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestStopAll) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+    string conditionName = "WL_HELD_BY_UID3";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid1 = 111;
+    int uid2 = 222;
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid1, "wl1", 1);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by uid2
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid2, "wl2", 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // TEST QUERY
+    const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+
+    // stop all event
+    LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_TRUE(changedCache[0]);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+
+    // TEST QUERY
+    const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+    // TEST QUERY
+    const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 864a9bb..4d6ba28 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -20,6 +20,8 @@
 <p>The version of SQLite depends on the version of Android. See the following table:
 <table style="width:auto;">
   <tr><th>Android API</th><th>SQLite Version</th></tr>
+  <tr><td>API 27</td><td>3.19</td></tr>
+  <tr><td>API 26</td><td>3.18</td></tr>
   <tr><td>API 24</td><td>3.9</td></tr>
   <tr><td>API 21</td><td>3.8</td></tr>
   <tr><td>API 11</td><td>3.7</td></tr>
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 37829f0..e30496f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         if (!sCompatibilityDone) {
-            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+            sAlwaysAssignFocus = true;
 
             sCompatibilityDone = true;
         }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c7e8dee..cca66d6 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@
     public void setStoppedState(IBinder token, boolean stopped) {
         synchronized (mLock) {
             int count = mViews.size();
-            for (int i = 0; i < count; i++) {
+            for (int i = count - 1; i >= 0; i--) {
                 if (token == null || mParams.get(i).token == token) {
                     ViewRootImpl root = mRoots.get(i);
+                    // Client might remove the view by "stopped" event.
                     root.setWindowStopped(stopped);
                 }
             }
diff --git a/core/tests/coretests/res/layout/add_column_in_table.xml b/core/tests/coretests/res/layout/add_column_in_table.xml
index 05f55a8..d929b02 100644
--- a/core/tests/coretests/res/layout/add_column_in_table.xml
+++ b/core/tests/coretests/res/layout/add_column_in_table.xml
@@ -18,6 +18,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <TableLayout android:id="@+id/table"
         android:layout_width="match_parent"
diff --git a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
index acbb10b..eac9b9d 100644
--- a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
+++ b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
@@ -21,6 +21,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
     <LinearLayout android:id="@+id/layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/core/tests/coretests/res/layout/focus_after_removal.xml b/core/tests/coretests/res/layout/focus_after_removal.xml
index f4e388d..84449d1 100644
--- a/core/tests/coretests/res/layout/focus_after_removal.xml
+++ b/core/tests/coretests/res/layout/focus_after_removal.xml
@@ -22,6 +22,7 @@
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
+    <requestFocus />
 
     <LinearLayout android:id="@+id/leftLayout"
         android:orientation="vertical"
diff --git a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
index ab76e29..6b3b5a7 100644
--- a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
+++ b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
@@ -22,6 +22,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <EditText
         android:id="@+id/editText"
diff --git a/core/tests/coretests/res/layout/visibility_callback.xml b/core/tests/coretests/res/layout/visibility_callback.xml
index 9034b3f..ff918f5 100644
--- a/core/tests/coretests/res/layout/visibility_callback.xml
+++ b/core/tests/coretests/res/layout/visibility_callback.xml
@@ -24,6 +24,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <LinearLayout
       android:orientation="vertical"
diff --git a/core/tests/coretests/src/android/util/ListScenario.java b/core/tests/coretests/src/android/util/ListScenario.java
index fa088a3..129484a 100644
--- a/core/tests/coretests/src/android/util/ListScenario.java
+++ b/core/tests/coretests/src/android/util/ListScenario.java
@@ -28,6 +28,7 @@
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
+
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -60,18 +61,18 @@
 
     // separators
     private Set<Integer> mUnselectableItems = new HashSet<Integer>();
-    
+
     private boolean mStackFromBottom;
 
     private int mClickedPosition = -1;
-    
+
     private int mLongClickedPosition = -1;
-    
+
     private int mConvertMisses = 0;
-    
+
     private int mHeaderViewCount;
     private boolean mHeadersFocusable;
-    
+
     private int mFooterViewCount;
     private LinearLayout mLinearLayout;
 
@@ -193,7 +194,7 @@
             mIncludeHeader = includeHeader;
             return this;
         }
-        
+
         /**
          * Sets the stacking direction
          * @param stackFromBottom
@@ -203,7 +204,7 @@
             mStackFromBottom = stackFromBottom;
             return this;
         }
-        
+
         /**
          * Sets whether the sum of the height of the list items must be at least the
          * height of the list view.
@@ -220,7 +221,7 @@
             mFadingEdgeScreenSizeFactor = fadingEdgeScreenSizeFactor;
             return this;
         }
-        
+
         /**
          * Set the number of header views to appear within the list
          */
@@ -246,7 +247,7 @@
             mFooterViewCount = footerViewCount;
             return this;
         }
-        
+
         /**
          * Sets whether the {@link ListScenario} will automatically set the
          * adapter on the list view. If this is false, the client MUST set it
@@ -278,7 +279,7 @@
      */
     protected void nothingSelected() {
     }
-    
+
     /**
      * Override this if you want to know when something has been clicked (perhaps
      * more importantly, that {@link android.widget.AdapterView.OnItemClickListener} has
@@ -287,7 +288,7 @@
     protected void positionClicked(int position) {
         setClickedPosition(position);
     }
-    
+
     /**
      * Override this if you want to know when something has been long clicked (perhaps
      * more importantly, that {@link android.widget.AdapterView.OnItemLongClickListener} has
@@ -303,7 +304,7 @@
 
         // for test stability, turn off title bar
         requestWindowFeature(Window.FEATURE_NO_TITLE);
-        
+
 
         mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
 
@@ -326,7 +327,7 @@
             header.setText("Header: " + i);
             mListView.addHeaderView(header);
         }
-        
+
         for (int i=0; i<mFooterViewCount; i++) {
             TextView header = new TextView(this);
             header.setText("Footer: " + i);
@@ -336,7 +337,7 @@
         if (params.mConnectAdapter) {
             setAdapter(mListView);
         }
-        
+
         mListView.setItemsCanFocus(mItemsFocusable);
         if (mStartingSelectionPosition >= 0) {
             mListView.setSelection(mStartingSelectionPosition);
@@ -360,11 +361,12 @@
                 positionClicked(position);
             }
         });
-        
+
         // set the fading edge length porportionally to the screen
         // height for test stability
         if (params.mFadingEdgeScreenSizeFactor != null) {
-            mListView.setFadingEdgeLength((int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));            
+            mListView.setFadingEdgeLength(
+                    (int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));
         } else {
             mListView.setFadingEdgeLength((int) ((64.0 / 480) * mScreenHeight));
         }
@@ -403,6 +405,7 @@
             mLinearLayout.addView(mListView);
             setContentView(mLinearLayout);
         }
+        mLinearLayout.restoreDefaultFocus();
     }
 
     /**
@@ -426,7 +429,7 @@
             }
         });
     }
-    
+
     /**
      * @return The newly created ListView widget.
      */
@@ -440,16 +443,16 @@
     protected Params createParams() {
         return new Params();
     }
-    
+
     /**
      * Sets an adapter on a ListView.
-     * 
+     *
      * @param listView The ListView to set the adapter on.
      */
     protected void setAdapter(ListView listView) {
         listView.setAdapter(new MyAdapter());
     }
-    
+
     /**
      * Read in and validate all of the params passed in by the scenario.
      * @param params
@@ -525,7 +528,7 @@
         if (!mIncludeHeader) {
             throw new IllegalArgumentException("no header above list");
         }
-        mHeaderTextView.setText(value);        
+        mHeaderTextView.setText(value);
     }
 
     /**
@@ -543,12 +546,12 @@
     }
 
     /**
-     * Convert a non-null view. 
+     * Convert a non-null view.
      */
     public View convertView(int position, View convertView, ViewGroup parent) {
         return ListItemFactory.convertText(convertView, getValueAtPosition(position), position);
     }
-    
+
     public void setClickedPosition(int clickedPosition) {
         mClickedPosition = clickedPosition;
     }
@@ -580,7 +583,7 @@
             }
         });
     }
-    
+
     /**
      * Return an item type for the specified position in the adapter. Override if your
      * adapter creates more than one type.
@@ -596,7 +599,7 @@
     public int getViewTypeCount() {
         return 1;
     }
-    
+
     /**
      * @return The number of times convertView failed
      */
@@ -647,7 +650,7 @@
             }
             return result;
         }
-        
+
         @Override
         public int getItemViewType(int position) {
             return ListScenario.this.getItemViewType(position);
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
index 1ba56ba..0d8d834 100644
--- a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
@@ -16,12 +16,12 @@
 
 package android.widget.layout.linear;
 
-import com.android.frameworks.coretests.R;
-
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
 
+import com.android.frameworks.coretests.R;
+
 public class LLOfTwoFocusableInTouchMode extends Activity {
 
     private View mButton1;
@@ -63,6 +63,7 @@
                 mB3Fired = true;
             }
         });
+        getWindow().getDecorView().restoreDefaultFocus();
     }
 
     public View getButton1() {
diff --git a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
index 98fbed3..9e49719 100644
--- a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
+++ b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
@@ -16,10 +16,9 @@
 
 package android.widget.listview;
 
-import android.util.InternalSelectionView;
-
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.InternalSelectionView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
@@ -80,6 +79,7 @@
 
 
         setContentView(combineAdjacent(mLeftListView, mRightListView));
+        getWindow().getDecorView().restoreDefaultFocus();
     }
 
     private static View combineAdjacent(View... views) {
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
index 8f971bb..edc60b5 100644
--- a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
@@ -16,15 +16,12 @@
 
 package android.widget.listview.focus;
 
-import android.widget.listview.ListHorizontalFocusWithinItemWins;
-
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.view.FocusFinder;
 import android.view.KeyEvent;
-import android.view.View;
 import android.widget.Button;
 import android.widget.ListView;
+import android.widget.listview.ListHorizontalFocusWithinItemWins;
 
 public class ListHorizontalFocusWithinItemWinsTest extends ActivityInstrumentationTestCase<ListHorizontalFocusWithinItemWins> {
 
@@ -51,12 +48,6 @@
     public void testPreconditions() {
         assertEquals("list position", 0, mListView.getSelectedItemPosition());
         assertTrue("mTopLeftButton.isFocused()", mTopLeftButton.isFocused());
-        assertEquals("global focus search to right from top left is bottom middle",
-                mBottomMiddleButton,
-                FocusFinder.getInstance().findNextFocus(mListView, mTopLeftButton, View.FOCUS_RIGHT));
-        assertEquals("global focus search to left from top right is bottom middle",
-                mBottomMiddleButton,
-                FocusFinder.getInstance().findNextFocus(mListView, mTopRightButton, View.FOCUS_LEFT));
     }
 
     @MediumTest
diff --git a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
index bd6977e..5a6110c 100644
--- a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
+++ b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
@@ -16,7 +16,6 @@
 
 package android.widget.touchmode;
 
-import android.widget.layout.linear.LLOfButtons1;
 import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
 import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
 import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
@@ -25,6 +24,8 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.KeyEvent;
 import android.widget.Button;
+import android.widget.layout.linear.LLOfButtons1;
+
 
 /**
  * Make sure focus isn't kept by buttons when entering touch mode.
@@ -52,7 +53,6 @@
     @MediumTest
     public void testPreconditions() {
         assertFalse("we should not be in touch mode", mActivity.isInTouchMode());
-        assertTrue("top button should have focus", mFirstButton.isFocused());
     }
 
     @MediumTest
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5d248c2..3fe75cf 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -812,7 +812,13 @@
             *outLen = encLen;
 
             if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
-                return (const char*)str;
+                // Reject malformed (non null-terminated) strings
+                if (str[encLen] != 0x00) {
+                    ALOGW("Bad string block: string #%d is not null-terminated",
+                          (int)idx);
+                    return NULL;
+                }
+              return (const char*)str;
             } else {
                 ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
                         (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d90caf9..d41db63 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -178,7 +178,7 @@
 }
 
 RenderPipelineType Properties::getRenderPipelineType() {
-    if (RenderPipelineType::NotInitialized != sRenderPipelineType) {
+    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
         return sRenderPipelineType;
     }
     char prop[PROPERTY_VALUE_MAX];
@@ -196,11 +196,17 @@
     return sRenderPipelineType;
 }
 
-#ifdef HWUI_GLES_WRAP_ENABLED
 void Properties::overrideRenderPipelineType(RenderPipelineType type) {
+#if !defined(HWUI_GLES_WRAP_ENABLED)
+    // If we're doing actual rendering then we can't change the renderer after it's been set.
+    // Unit tests can freely change this as often as it wants, though, as there's no actual
+    // GL rendering happening
+    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
+        return;
+    }
+#endif
     sRenderPipelineType = type;
 }
-#endif
 
 bool Properties::isSkiaEnabled() {
     auto renderType = getRenderPipelineType();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index fbd9b7a..9c30e4a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -255,10 +255,8 @@
 
     static bool skpCaptureEnabled;
 
-// Used for testing only to change the render pipeline.
-#ifdef HWUI_GLES_WRAP_ENABLED
+    // Used for testing only to change the render pipeline.
     static void overrideRenderPipelineType(RenderPipelineType);
-#endif
 
 private:
     static ProfileType sProfileType;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6657fe8..91b35c2 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -144,22 +144,25 @@
 // ----------------------------------------------------------------------------
 
 inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
-                                         sk_sp<SkColorFilter> colorFilter) {
-    if ((origPaint && origPaint->isAntiAlias()) || colorFilter) {
+                                         sk_sp<SkColorFilter> colorSpaceFilter) {
+    if ((origPaint && origPaint->isAntiAlias()) || colorSpaceFilter) {
         if (origPaint) {
             *tmpPaint = *origPaint;
         }
 
-        sk_sp<SkColorFilter> filter;
-        if (colorFilter && tmpPaint->getColorFilter()) {
-            filter = SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorFilter);
-            LOG_ALWAYS_FATAL_IF(!filter);
-        } else {
-            filter = colorFilter;
+        if (colorSpaceFilter) {
+            if (tmpPaint->getColorFilter()) {
+                tmpPaint->setColorFilter(
+                        SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
+            } else {
+                tmpPaint->setColorFilter(colorSpaceFilter);
+            }
+            LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
         }
 
+
+        // disabling AA on bitmap draws matches legacy HWUI behavior
         tmpPaint->setAntiAlias(false);
-        tmpPaint->setColorFilter(filter);
         return tmpPaint;
     } else {
         return origPaint;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 1d8cdd6..a693e68 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -602,6 +602,7 @@
         mDeviceWaitIdle(mBackendContext->fDevice);
     }
 
+    SkASSERT(surface->mBackbuffers);
     VulkanSurface::BackbufferInfo* backbuffer =
             surface->mBackbuffers + surface->mCurrentBackbufferIndex;
     GrVkImageInfo* imageInfo;
@@ -683,6 +684,7 @@
 }
 
 int VulkanManager::getAge(VulkanSurface* surface) {
+    SkASSERT(surface->mBackbuffers);
     VulkanSurface::BackbufferInfo* backbuffer =
             surface->mBackbuffers + surface->mCurrentBackbufferIndex;
     if (mSwapBehavior == SwapBehavior::Discard ||
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b9898a7..c319c9e 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -59,11 +59,11 @@
     VkSurfaceKHR mVkSurface = VK_NULL_HANDLE;
     VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
 
-    BackbufferInfo* mBackbuffers;
+    BackbufferInfo* mBackbuffers = nullptr;
     uint32_t mCurrentBackbufferIndex;
 
     uint32_t mImageCount;
-    VkImage* mImages;
+    VkImage* mImages = nullptr;
     ImageInfo* mImageInfos;
     uint16_t mCurrentTime = 0;
 };
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f1ff939..8c0ca5c 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -68,6 +68,7 @@
   --onscreen           Render tests on device screen. By default tests
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
+  --renderer=TYPE      Sets the render pipeline to use. May be opengl, skiagl, or skiavk
 )");
 }
 
@@ -146,6 +147,20 @@
     return true;
 }
 
+static bool setRenderer(const char* renderer) {
+    if (!strcmp(renderer, "opengl")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL);
+    } else if (!strcmp(renderer, "skiagl")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+    } else if (!strcmp(renderer, "skiavk")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
+    } else {
+        fprintf(stderr, "Unknown format '%s'", renderer);
+        return false;
+    }
+    return true;
+}
+
 // For options that only exist in long-form. Anything in the
 // 0-255 range is reserved for short options (which just use their ASCII value)
 namespace LongOpts {
@@ -158,6 +173,7 @@
     BenchmarkFormat,
     Onscreen,
     Offscreen,
+    Renderer,
 };
 }
 
@@ -172,6 +188,7 @@
         {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
+        {"renderer", required_argument, nullptr, LongOpts::Renderer},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -252,6 +269,16 @@
                 }
                 break;
 
+            case LongOpts::Renderer:
+                if (!optarg) {
+                    error = true;
+                    break;
+                }
+                if (!setRenderer(optarg)) {
+                    error = true;
+                }
+                break;
+
             case LongOpts::Onscreen:
                 gOpts.renderOffscreen = false;
                 break;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20405d3..7afe267 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@
         SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN,                           SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
     }
 
     /**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index dab7632a..58976ca 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -43,17 +43,16 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@
      */
     private boolean querySoundEffectsEnabled(int user) {
         return Settings.System.getIntForUser(getContext().getContentResolver(),
-                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+                && !areSystemSoundsZenModeBlocked(getContext());
     }
 
+    private boolean areSystemSoundsZenModeBlocked(Context context) {
+        int zenMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ZEN_MODE, 0);
+
+        switch (zenMode) {
+            case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+            case Settings.Global.ZEN_MODE_ALARMS:
+                return true;
+            case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                final NotificationManager noMan = (NotificationManager) context
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
+                return (noMan.getNotificationPolicy().priorityCategories
+                        & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+            case Settings.Global.ZEN_MODE_OFF:
+            default:
+                return false;
+        }
+    }
 
     /**
      *  Load Sound effects.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca9..a35ba9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -28,6 +28,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
@@ -1691,6 +1692,8 @@
             mUiOffloadThread.submit(() -> {
                 // If the stream is muted, don't play the sound
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+                // If DND blocks the sound, don't play the sound
+                if (areSystemSoundsZenModeBlocked(mContext)) return;
 
                 int id = mLockSounds.play(soundId,
                         mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1705,25 @@
         }
     }
 
+    private boolean areSystemSoundsZenModeBlocked(Context context) {
+        int zenMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ZEN_MODE, 0);
+
+        switch (zenMode) {
+            case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+            case Settings.Global.ZEN_MODE_ALARMS:
+                return true;
+            case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                final NotificationManager noMan = (NotificationManager) context
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
+                return (noMan.getNotificationPolicy().priorityCategories
+                        & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+            case Settings.Global.ZEN_MODE_OFF:
+            default:
+                return false;
+        }
+    }
+
     private void playTrustedSound() {
         playSound(mTrustedSoundId);
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f61cec9..1e9fab5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -835,7 +835,8 @@
         // alarm restrictions
         final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
         // total silence restrictions
-        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+                || areAllBehaviorSoundsMuted();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
@@ -855,9 +856,11 @@
         }
     }
 
+
     @VisibleForTesting
     protected void applyRestrictions(boolean mute, int usage) {
         final String[] exceptionPackages = null; // none (for now)
+
         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                 exceptionPackages);
@@ -866,6 +869,12 @@
                 exceptionPackages);
     }
 
+    private boolean areAllBehaviorSoundsMuted() {
+        return !mConfig.allowAlarms  && !mConfig.allowMediaSystemOther && !mConfig.allowReminders
+                && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents
+                && !mConfig.allowRepeatCallers;
+    }
+
     private void applyZenToRingerMode() {
         if (mAudioManager == null) return;
         // force the ringer mode into compliance
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 5ea0e1d..e693e5a 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -23,6 +23,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
@@ -116,6 +117,7 @@
     private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
+    private int mOriginalDockedSide = DOCKED_INVALID;
     private boolean mAnimatingForMinimizedDockedStack;
     private boolean mAnimationStarted;
     private long mAnimationStartTime;
@@ -408,6 +410,31 @@
         mDockedStackListeners.finishBroadcast();
     }
 
+    /**
+     * Checks if the primary stack is allowed to dock to a specific side based on its original dock
+     * side.
+     *
+     * @param dockSide the side to see if it is valid
+     * @return true if the side provided is valid
+     */
+    boolean canPrimaryStackDockTo(int dockSide) {
+        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+            // Side is the same as original side
+            if (dockSide == mOriginalDockedSide) {
+                return true;
+            }
+            // Special rule that the top in portrait is always valid
+            if (dockSide == DOCKED_TOP) {
+                return true;
+            }
+            // Only if original docked side was top in portrait will allow left side for landscape
+            if (dockSide == DOCKED_LEFT && mOriginalDockedSide == DOCKED_TOP) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void notifyDockedStackExistsChanged(boolean exists) {
         // TODO(multi-display): Perform all actions only for current display.
         final int size = mDockedStackListeners.beginBroadcast();
@@ -430,8 +457,11 @@
                 inputMethodManagerInternal.hideCurrentInputMethod();
                 mImeHideRequested = true;
             }
+            final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+            mOriginalDockedSide = stack.getDockSide();
             return;
         }
+        mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 170feac..dde7946 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -442,7 +442,7 @@
         mTmpRect2.set(mBounds);
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (inSplitScreenPrimaryWindowingMode()) {
-            repositionDockedStackAfterRotation(mTmpRect2);
+            repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
             snapDockedStackAfterRotation(mTmpRect2);
             final int newDockSide = getDockSide(mTmpRect2);
 
@@ -466,14 +466,14 @@
     }
 
     /**
-     * Some dock sides are not allowed by the policy. This method queries the policy and moves
-     * the docked stack around if needed.
+     * Some primary split screen sides are not allowed by the policy. This method queries the policy
+     * and moves the primary stack around if needed.
      *
-     * @param inOutBounds the bounds of the docked stack to adjust
+     * @param inOutBounds the bounds of the primary stack to adjust
      */
-    private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+    private void repositionPrimarySplitScreenStackAfterRotation(Rect inOutBounds) {
         int dockSide = getDockSide(inOutBounds);
-        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+        if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
             return;
         }
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
diff --git a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
index cbe9650..8ac6481 100644
--- a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,7 +17,7 @@
 package com.android.server.notification;
 
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -100,5 +100,34 @@
                 AudioAttributes.USAGE_GAME);
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
                 AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_UNKNOWN);
+    }
+
+    @Test
+    public void testZenAllCannotBypass() {
+        // Only audio attributes with SUPPRESIBLE_NEVER can bypass
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mConfig.allowAlarms = false;
+        mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+        mZenModeHelperSpy.mConfig.allowReminders = false;
+        mZenModeHelperSpy.mConfig.allowCalls = false;
+        mZenModeHelperSpy.mConfig.allowMessages = false;
+        mZenModeHelperSpy.mConfig.allowEvents = false;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+        assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        assertFalse(mZenModeHelperSpy.mConfig.allowReminders);
+        assertFalse(mZenModeHelperSpy.mConfig.allowCalls);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMessages);
+        assertFalse(mZenModeHelperSpy.mConfig.allowEvents);
+        assertFalse(mZenModeHelperSpy.mConfig.allowRepeatCallers);
+        mZenModeHelperSpy.applyRestrictions();
+
+        for (int usage : AudioAttributes.SDK_USAGES) {
+            boolean shouldMute = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage)
+                    != AudioAttributes.SUPPRESSIBLE_NEVER;
+            verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(shouldMute, usage);
+        }
     }
 }
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index f601d8b..4b827d2 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  *  Encapsulates the telecom audio state, including the current audio routing, supported audio
  *  routing and mute.
  */
 public final class CallAudioState implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+            flag=true)
+    public @interface CallAudioRoute {}
+
     /** Direct the audio stream through the device's earpiece. */
     public static final int ROUTE_EARPIECE      = 0x00000001;
 
@@ -55,6 +74,8 @@
     private final boolean isMuted;
     private final int route;
     private final int supportedRouteMask;
+    private final BluetoothDevice activeBluetoothDevice;
+    private final Collection<BluetoothDevice> supportedBluetoothDevices;
 
     /**
      * Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@
      * {@link #ROUTE_WIRED_HEADSET}
      * {@link #ROUTE_SPEAKER}
      */
-    public CallAudioState(boolean muted, int route, int supportedRouteMask) {
-        this.isMuted = muted;
+    public CallAudioState(boolean muted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask) {
+        this(muted, route, supportedRouteMask, null, Collections.emptyList());
+    }
+
+    /** @hide */
+    public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask,
+            @Nullable BluetoothDevice activeBluetoothDevice,
+            @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+        this.isMuted = isMuted;
         this.route = route;
         this.supportedRouteMask = supportedRouteMask;
+        this.activeBluetoothDevice = activeBluetoothDevice;
+        this.supportedBluetoothDevices = supportedBluetoothDevices;
     }
 
     /** @hide */
@@ -84,6 +116,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = state.activeBluetoothDevice;
+        supportedBluetoothDevices = state.getSupportedBluetoothDevices();
     }
 
     /** @hide */
@@ -92,6 +126,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = null;
+        supportedBluetoothDevices = Collections.emptyList();
     }
 
     @Override
@@ -103,17 +139,32 @@
             return false;
         }
         CallAudioState state = (CallAudioState) obj;
-        return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
-                getSupportedRouteMask() == state.getSupportedRouteMask();
+        if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+            return false;
+        }
+        for (BluetoothDevice device : supportedBluetoothDevices) {
+            if (!state.supportedBluetoothDevices.contains(device)) {
+                return false;
+            }
+        }
+        return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+                state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+                state.getSupportedRouteMask();
     }
 
     @Override
     public String toString() {
+        String bluetoothDeviceList = supportedBluetoothDevices.stream()
+                .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
         return String.format(Locale.US,
-                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+                        "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
                 isMuted,
                 audioRouteToString(route),
-                audioRouteToString(supportedRouteMask));
+                audioRouteToString(supportedRouteMask),
+                activeBluetoothDevice,
+                bluetoothDeviceList);
     }
 
     /**
@@ -126,6 +177,7 @@
     /**
      * @return The current audio route being used.
      */
+    @CallAudioRoute
     public int getRoute() {
         return route;
     }
@@ -133,11 +185,27 @@
     /**
      * @return Bit mask of all routes supported by this call.
      */
+    @CallAudioRoute
     public int getSupportedRouteMask() {
         return supportedRouteMask;
     }
 
     /**
+     * @return The {@link BluetoothDevice} through which audio is being routed.
+     *         Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+     */
+    public BluetoothDevice getActiveBluetoothDevice() {
+        return activeBluetoothDevice;
+    }
+
+    /**
+     * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+     */
+    public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+        return supportedBluetoothDevices;
+    }
+
+    /**
      * Converts the provided audio route into a human readable string representation.
      *
      * @param route to convert into a string.
@@ -177,7 +245,13 @@
             boolean isMuted = source.readByte() == 0 ? false : true;
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
-            return new CallAudioState(isMuted, route, supportedRouteMask);
+            BluetoothDevice activeBluetoothDevice = source.readParcelable(
+                    ClassLoader.getSystemClassLoader());
+            List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+            source.readParcelableList(supportedBluetoothDevices,
+                    ClassLoader.getSystemClassLoader());
+            return new CallAudioState(isMuted, route,
+                    supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
 
         @Override
@@ -202,6 +276,8 @@
         destination.writeByte((byte) (isMuted ? 1 : 0));
         destination.writeInt(route);
         destination.writeInt(supportedRouteMask);
+        destination.writeParcelable(activeBluetoothDevice, 0);
+        destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
     }
 
     private static void listAppend(StringBuffer buffer, String str) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8ba934c..ffb5e93 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -819,7 +820,7 @@
         public void onConnectionEvent(Connection c, String event, Bundle extras) {}
         /** @hide */
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
-        public void onAudioRouteChanged(Connection c, int audioRoute) {}
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
         public void onRttInitiationSuccess(Connection c) {}
         public void onRttInitiationFailure(Connection c, int reason) {}
         public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -2576,7 +2577,29 @@
      */
     public final void setAudioRoute(int route) {
         for (Listener l : mListeners) {
-            l.onAudioRouteChanged(this, route);
+            l.onAudioRouteChanged(this, route, null);
+        }
+    }
+
+    /**
+     *
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * <p>
+     * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+     * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+     * <p>
+     * See also {@link InCallService#requestBluetoothAudio(String)}
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        for (Listener l : mListeners) {
+            l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         }
     }
 
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index a81fba9..35804f6 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@
         }
 
         @Override
-        public void onAudioRouteChanged(Connection c, int audioRoute) {
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
             String id = mIdByConnection.get(c);
             if (id != null) {
-                mAdapter.setAudioRoute(id, audioRoute);
+                mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
             }
         }
 
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 111fcc7..92a9dc2 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@
      * @param callId The unique ID of the call.
      * @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
      */
-    void setAudioRoute(String callId, int audioRoute) {
-        Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+    void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+        Log.v(this, "setAudioRoute: %s %s %s", callId,
+                CallAudioState.audioRouteToString(audioRoute),
+                bluetoothAddress);
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+                adapter.setAudioRoute(callId, audioRoute,
+                        bluetoothAddress, Log.getExternalSession());
             } catch (RemoteException ignored) {
             }
         }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4..3fbdeb1 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@
                 case MSG_SET_AUDIO_ROUTE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        mDelegate.setAudioRoute((String) args.arg1, args.argi1,
-                                (Session.Info) args.arg2);
+                        mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+                                (Session.Info) args.arg3);
                     } finally {
                         args.recycle();
                     }
@@ -548,12 +548,12 @@
 
         @Override
         public final void setAudioRoute(String connectionId, int audioRoute,
-                Session.Info sessionInfo) {
-
+                String bluetoothAddress, Session.Info sessionInfo) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = connectionId;
             args.argi1 = audioRoute;
-            args.arg2 = sessionInfo;
+            args.arg2 = bluetoothAddress;
+            args.arg3 = sessionInfo;
             mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
         }
 
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 9559a28..9bf0467 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -128,7 +129,22 @@
      */
     public void setAudioRoute(int route) {
         try {
-            mAdapter.setAudioRoute(route);
+            mAdapter.setAudioRoute(route, null);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        try {
+            mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         } catch (RemoteException e) {
         }
     }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index e384d46..d558bba 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -377,6 +379,22 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        if (mPhone != null) {
+            mPhone.requestBluetoothAudio(bluetoothAddress);
+        }
+    }
+
+    /**
      * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
      * to start displaying in-call information to the user. Each instance of {@code InCallService}
      * will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 066f6c2..421b1a4 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,7 +17,9 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 
 import java.util.Collections;
@@ -295,6 +297,18 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+    }
+
+    /**
      * Turns the proximity sensor on. When this request is made, the proximity sensor will
      * become active, and the touch screen and display will be turned off when the user's face
      * is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2cc4314..85906ad 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@
         }
 
         @Override
-        public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+        public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+                Session.Info sessionInfo) {
             if (hasConnection(callId)) {
                 // TODO(3pcalls): handle this for remote connections.
                 // Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index d20da18..da2015f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -103,7 +103,8 @@
 
     void removeExtras(String callId, in List<String> keys, in Session.Info sessionInfo);
 
-    void setAudioRoute(String callId, int audioRoute, in Session.Info sessionInfo);
+    void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+            in Session.Info sessionInfo);
 
     void onConnectionEvent(String callId, String event, in Bundle extras,
     in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 73fa29a..bce3392 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -39,7 +39,7 @@
 
     void mute(boolean shouldMute);
 
-    void setAudioRoute(int route);
+    void setAudioRoute(int route, String bluetoothAddress);
 
     void playDtmfTone(String callId, char digit);
 
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 89ae9e8..a34d51b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -37,35 +37,44 @@
 
 namespace aapt {
 
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
-                       const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
-  BigBuffer buffer(4096);
-  XmlFlattenerOptions options = {};
-  options.use_utf16 = utf16;
-  XmlFlattener flattener(&buffer, options);
-  if (!flattener.Consume(context, &xml)) {
-    return false;
+class ISerializer {
+ public:
+  ISerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
+
+  virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+                            IArchiveWriter* writer) = 0;
+  virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
+  virtual bool SerializeFile(const FileReference* file, IArchiveWriter* writer) = 0;
+
+  bool CopyFileToArchive(const FileReference* file, IArchiveWriter* writer) {
+    uint32_t compression_flags = file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+    if (!io::CopyFileToArchive(context_, file->file, *file->path, compression_flags, writer)) {
+      context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                        << "failed to copy file " << *file->path);
+      return false;
+    }
+
+    return true;
   }
-  io::BigBufferInputStream input_stream(&buffer);
-  return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
-                                      writer);
-}
+
+  virtual ~ISerializer() = default;
+
+ protected:
+  IAaptContext* context_;
+  Source source_;
+};
 
 bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
-                                const TableFlattenerOptions& options, IArchiveWriter* writer) {
-  if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+                                ISerializer* serializer, IArchiveWriter* writer) {
+  if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "failed to serialize AndroidManifest.xml");
     return false;
   }
 
-  BigBuffer buffer(4096);
-  TableFlattener table_flattener(options, &buffer);
-  if (!table_flattener.Consume(context, apk->GetResourceTable())) {
-    return false;
-  }
-
-  io::BigBufferInputStream input_stream(&buffer);
-  if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
-                                    ArchiveEntry::kAlign, writer)) {
+  if (!serializer->SerializeTable(apk->GetResourceTable(), writer)) {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "failed to serialize the resource table");
     return false;
   }
 
@@ -76,51 +85,16 @@
           const FileReference* file = ValueCast<FileReference>(config_value->value.get());
           if (file != nullptr) {
             if (file->file == nullptr) {
-              context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+              context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
                                               << "no file associated with " << *file);
               return false;
             }
 
-            if (file->type == ResourceFile::Type::kProtoXml) {
-              unique_ptr<io::InputStream> in = file->file->OpenInputStream();
-              if (in == nullptr) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to open file " << *file->path);
-                return false;
-              }
-
-              pb::XmlNode pb_node;
-              io::ZeroCopyInputAdaptor adaptor(in.get());
-              if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to parse proto XML " << *file->path);
-                return false;
-              }
-
-              std::string error;
-              unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
-              if (xml == nullptr) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to deserialize proto XML "
-                                                 << *file->path << ": " << error);
-                return false;
-              }
-
-              if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to serialize XML " << *file->path);
-                return false;
-              }
-            } else {
-              if (!io::CopyFileToArchive(context, file->file, *file->path,
-                                         file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
-                                         writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to copy file " << *file->path);
-                return false;
-              }
+            if (!serializer->SerializeFile(file, writer)) {
+              context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                               << "failed to serialize file " << *file->path);
+              return false;
             }
-
           } // file
         } // config_value
       } // entry
@@ -129,6 +103,84 @@
   return true;
 }
 
+
+class BinarySerializer : public ISerializer {
+ public:
+  BinarySerializer(IAaptContext* context, const Source& source,
+                   const TableFlattenerOptions& options)
+      : ISerializer(context, source), tableFlattenerOptions_(options) {}
+
+  bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+                    IArchiveWriter* writer) {
+    BigBuffer buffer(4096);
+    XmlFlattenerOptions options = {};
+    options.use_utf16 = utf16;
+    XmlFlattener flattener(&buffer, options);
+    if (!flattener.Consume(context_, xml)) {
+      return false;
+    }
+
+    io::BigBufferInputStream input_stream(&buffer);
+    return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
+                                        writer);
+  }
+
+  bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) {
+    BigBuffer buffer(4096);
+    TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
+    if (!table_flattener.Consume(context_, table)) {
+      return false;
+    }
+
+    io::BigBufferInputStream input_stream(&buffer);
+    return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+                                        ArchiveEntry::kAlign, writer);
+  }
+
+  bool SerializeFile(const FileReference* file, IArchiveWriter* writer) {
+    if (file->type == ResourceFile::Type::kProtoXml) {
+      unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+      if (in == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to open file " << *file->path);
+        return false;
+      }
+
+      pb::XmlNode pb_node;
+      io::ZeroCopyInputAdaptor adaptor(in.get());
+      if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to parse proto XML " << *file->path);
+        return false;
+      }
+
+      std::string error;
+      unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+      if (xml == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to deserialize proto XML "
+                                          << *file->path << ": " << error);
+        return false;
+      }
+
+      if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to serialize proto XML " << *file->path);
+        return false;
+      }
+    } else {
+      return CopyFileToArchive(file, writer);
+    }
+
+    return true;
+  }
+
+ private:
+  TableFlattenerOptions tableFlattenerOptions_;
+
+  DISALLOW_COPY_AND_ASSIGN(BinarySerializer);
+};
+
 class Context : public IAaptContext {
  public:
   Context() : mangler_({}), symbols_(&mangler_) {
@@ -222,7 +274,8 @@
   if (writer == nullptr) {
     return 1;
   }
-  return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+  BinarySerializer serializer(&context, apk->GetSource(), options);
+  return ConvertProtoApkToBinaryApk(&context, std::move(apk), &serializer, writer.get()) ? 0 : 1;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d39f43e8..8b3a670 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -140,12 +140,27 @@
   return decl;
 }
 
+// Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by
+// replacing nonconforming characters with underscores.
+//
+// See frameworks/base/core/java/android/content/pm/PackageParser.java which
+// checks this at runtime.
 static std::string MakePackageSafeName(const std::string &name) {
   std::string result(name);
+  bool first = true;
   for (char &c : result) {
-    if (c == '-') {
-      c = '_';
+    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+      first = false;
+      continue;
     }
+    if (!first) {
+      if (c >= '0' && c <= '9') {
+        continue;
+      }
+    }
+
+    c = '_';
+    first = false;
   }
   return result;
 }
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 9c33135..0c527f6 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -24,15 +24,16 @@
 
 TEST(UtilTest, SplitNamesAreSanitized) {
     AppInfo app_info{"com.pkg"};
-    SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+    SplitConstraints split_constraints{
+        {test::ParseConfigOrDie("en-rUS-land"), test::ParseConfigOrDie("b+sr+Latn")}};
 
     const auto doc = GenerateSplitManifest(app_info, split_constraints);
     const auto &root = doc->root;
     EXPECT_EQ(root->name, "manifest");
-    // split names cannot contain hyphens
-    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+    // split names cannot contain hyphens or plus signs.
+    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.b_sr_Latn_en_rUS_land");
     // but we should use resource qualifiers verbatim in 'targetConfig'.
-    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "b+sr+Latn,en-rUS-land");
 }
 
 }  // namespace aapt